JS App Continuous Deployment (for every branch) using CircleCI & S3

Recently at Viget, we've been developing a number of highly interactive client-side applications. We've long been believers in continuous integration and continuous deployment, which typically involves the CI service (CircleCI, in our case) running a Capistrano deployment to push the latest updates to the integration environment after all the other checks pass.

What's cool with a client-side-only app is that your app compiles down to a series of static files, so your "integration environment" can be a file hosting solution like Amazon's S3. And what's EXTRA cool is that this technique allows for continuous deployment for every branch you push up, without having to deal with database discrepencies, managing virtual hosts, etc. Interested? Good. Let's see how it's done.

Create an S3 Bucket

First, let's create an AWS S3 bucket with the proper configuration for hosting our app:

  1. Log In and navigate to S3 settings
  2. Click "Create bucket"
  3. Give it a name ("example-bucket"), and a region ("US East (N. Virginia)")
  4. Click "Next" and keep clicking "Next" to finish the bucket creation
  5. Click on the bucket and go to the "Properties" tab
  6. Click "Static website hosting" and choose "Use this bucket to host a website"
  7. Set the index document: index.html
  8. Click "Save"
  9. Go to the "Permissions" tab, then the "Bucket Policy" tab, and fill in:
    "Id": "Policy1483481703857",
    "Statement": [
            "Sid": "Stmt1483481698457",
            "Effect": "Allow",
            "Principal": "",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::example-bucket/"
  10. Click "Save"

Configure Application AWS Credentials

Next, let's set up our application's scripts and configuration for deploying to our newly created S3 bucket. We'll want to keep our AWS credentials out of the repository. Create a .env.example file with the following:


Then, add .env to .gitignore (echo .env >> .gitignore), copy the example file over (cp .env.example .env), and then update .env to include the proper AWS credentials.

Create Deploy Scripts

Next, let's create a npm/yarn script (build is just for example and not necessary). Add the following to package.json:

  "scripts": {
    "build": "mkdir -p build && cp index.html build/",
    "deploy": "bash -e tasks/deploy.sh $BRANCH_NAME"

And create a deploy script (tasks/deploy.sh):

# load the .env file if deploying from a dev machine (not CI)
if [ ! "$CIRCLECI" == true ]; then
  source .env

# make the values from .env available to the following commands
export NODE_ENV=production

yarn build

# Deploy to S3 bucket
aws s3 sync build/ $S3_BUCKET_URL$BRANCH_NAME

Note $BRANCH_NAME = '' on CircleCI for master branch which will deploy master to the root of your bucket.

Configure CircleCI

Now let's configure CircleCI to deploy each branch to a nested folder under root on our S3 bucket.

  1. Configure CircleCI to build your project.
  2. Go to Settings -> Environment Variables and add the same values that you're using in your .env file:
  3. Add/modify a circle.yml config file:
# circle.yml

    - yarn
    - ~/.cache/yarn

    branch: master
      - yarn deploy
    branch: /^(?!master).*$/
      - BRANCH_NAME=$CIRCLE_BRANCH yarn deploy

The last conditional is to set the BRANCH_NAME ENV variable for non-master branches.

BONUS! Slack Notifications

It's dirt simple to configure your deploy script to post a message in Slack with the URl of the newly deployed branch. From within the Slack app:

  1. Click the Settings button, then "Add an app or integration", then "Manage" up at the top
  2. Click "Custom Integrations"
  3. Click "Incoming Webhooks"
  4. Click "Add configuration"
  5. Select the channel you want to notify from the drop-down.
  6. Click "Add Incoming WebHooks integration"
  7. Customize Name (optional): Deploybot
  8. Customize Icon (optional):
  9. Copy & paste the generated Webhook URL into ./tasks/deploy.sh
aws s3 sync build/ $S3_BUCKET_URL$BRANCH_NAME && \
  curl -X POST -d "{\"text\":\"Just deployed $BRANCH_NAME to \"}" https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK_TOKEN_HERE

BONUS 2! Example README Documentation

## Deployment

The application is deployed to an AWS S3 bucket. The credentials and bucket are configured in `.env`. To assist, run:

cp .env.example .env

Next replace all placeholder values in `.env` with valid AWS credentials. See 1Password for account details.

Then, install the AWS CLI tool.


brew install awscli

To deploy:

yarn deploy

To manually deploy a specific branch, check out that branch and then run:

BRANCH_NAME=feature-branch-name yarn deploy

On success, the branch name will be available at a nested directory (under root) with the name you specified. For example: http://example-bucket.s3-website-us-east-1.amazonaws.com/feature-branch-name

Mike is a developer who uses his computer and electrical engineering degrees to craft complex but streamlined back-end systems for clients such as Shure and Privia Medical Group.

More articles by Mike