The end goal is to have a workflow that allows us to push code changes up to
GitHub and have them seamlessly deployed on AWS ECS. To accomplish this, we'll
have Semaphore watch our GitHub repository, test it whenever changes are made,
and deploy it if the branch being updated is master.

Prerequisites

In order to keep things productive and brief we'll have to that you are familiar
with:

You don't need to be an expert, however, the less experience you have in these 3
areas, the more "whys" you may end up with. We'll make it a point to describe
topics and areas specific to this workflow, but won't stop and explain what a
unit test is or dive deep into concepts surrounding containers for example.

Pulling Down the Sample Application

Instead of going through a step-by-step of setting up a brand new Node
application with tests etc., we'll pull down a very simple one. It includes the
following:

It simply uses the official Node Docker image at version 6.10.0 as the starting
point, creates a directory for our code at /usr/local/app, copies the the code
of our current directory INTO the /usr/local/app directory and finally runs
npm start.

Before we build it, let's go ahead and set up a repository for it on ECR.

Head over to the AWS Console and Login,

Make sure that you're in the N. Virginia region, by checking in the top
right of the AWS Console navigation bar,

Click on EC2 Container Service,

In the sidebar, click on Repositories,

Click on Create Repository.

For Repository Name name it semaphore/node-app, and

Click Next Step.

After doing so, we'll receive a list of commands to run in order to push our
repository up to ECR.

Now, we simply run all of those commands.

In the back in our shell run the first command:

$ $(aws ecr get-login --region us-east-1)

The command as noted in the AWS Console will give us the command to login, but
not actually run it. Enclosing it with the $() will not only fetch the command
but also run it.

Run the command to build our Docker image:

$ docker build -t semaphore/node-app .

Make sure that you're in the root directory of our sample application.

The reason we need to tag it in such a way is so that Docker recognizes we want
to push to a URL that's not Docker Hub. If we, for example, simply docker push'd
our semaphore/node-app:latest, Docker send the image to Docker Hub.

We're saying "Create an ECS stack that has 1 EC2 instance for running our
containers. Use our Docker image in ECR. Deploy it into two of the subnets in
AWS's default VPC."" Side note: for the VPC and Subnets, feel free to use your
own or any of the default AWS subnets.

Click Next,

On the Options page, leave everything as-is and click Next,

At the bottom under Capabilities, click the I acknowledge that AWS CloudFormation might create IAM resources. checkbox, and

Click Create.

It will redirect us back to the main CloudFormation page. If we select our stack
SemaphoreCI and in the bottom window pane select the Events tab, we can
see updates of our resources being created.

Once it's complete, navigate to the Outputs tab, and we'll see that there's
an output called EcsDNS. This is the DNS of our load balancer. Copy it and
paste it into a browser to se our simple Node application live on AWS ECS.

Creating the IAM User and Policy for Semaphore CI

In order for Semaphore to connect with and update our project, it needs
permissions on our behalf from AWS. The workflow to accomplish this requires us
to set up an IAM policy with the permissions that are needed to build our stack,
and create a user for Semaphore and attach this policy to it.

Head over to the AWS Console and click on IAM under Security, Identity & Compliance,

This document will allow any user with this Policy to update any of the
resources needed to update a stack particularly like ours. If we wanted to lock
it down, we'd need to change "Resources": "*" to include every resource that
could be affected by a Semaphore stack update. We'll leave it open for now.

IAM policies can be quite overwhelming, but once again the focus here is on
the CI/CD workflow and not IAM. For a general overview and explanation of IAM
policies check out this article:

Before we dive into the details here, let's take a quick aside and explain the
over all architecture here.

Our AWS infrastructure is set up and managed by CloudFormation. This means that
if we make any changes to the template or its parameters, the entire stack will
update for us. Only what needs to be changed will be changed.

This makes it incredibly easy to set up continuous deploy. All we have to do is:

Update our code base,

Build a new Docker image with the updated code,

Push the new Docker image up to AWS ECR, and

Update the CloudFormation template via AWS CLI with the new image.

With those 4 simple steps, AWS will redeploy our application. The bonus here is
that AWS will deploy it in such a way that it won't just hard kill the existing
containers. Instead, it will deploy the new ones, drain the old ones and
incrementally redirect traffic accordingly.

To accomplish step #4 from above, our codebase includes a very simple script:

This script builds and pushes our Docker image up to AWS ECR and then updates
our CloudFormation template. We have some environment variables we're using,
some of which come predefined in SemaphoreCI's build environment, and some that
we'll define on our own.

Let's set up Semaphore to deploy our build.

In the Your Projects screen on SemaphoreCI, select Add New project,

For Select Repository select the GitHub repo with our code in it,

On Select Branch select master,

SemaphoreCI will then try and analyze the project in order to determine how
best to build it.

On Select Platform choose Docker,

For Setup Docker Registry select Amazon EC2 Container Registry,

Input the Access Key ID and Secret Key from our credentials.csv we downloaded
from IAM. For Region, assuming you're in N. Virginia, use us-east-1,

Click Save & Continue,

This will take us to the build screen, but we don't want to do so just yet.
First we need to add all of our environment variables.

Click on the Semaphore logo to return to the Your Projects screen,

Next, go to the newly created project, click on the "gear" to go into the Settings for the project,