Use CircleCI and automate your deploys for free ๐Ÿšข

In a future post, we will discuss how to implement an AWS S3 bucket as hosting for your web application, and an AWS CloudFront distribution as your content distribution network to deliver your web application in a scalable way.

But first, we need an efficient way to manage several environments.

After all, if you have one million users you donโ€™t want to mess up your site by using a bad CLI command.

We need to have our infrastructure automatized, and those configurations must be INSIDE a version control tool like GIT or SVN.

(just kidding, who use SVN these days?)

Iโ€™m a big fan of CircleCI, I have been using it since version 1 in 2015 when I was looking for a cheap alternative to TravisCI but also an easier alternative to Jenkins.

Letโ€™s deep dive into a Continuous Integration solution for your web application using CircleCI.

Table of contents

Why continuous integration and delivery ? ๐Ÿค”

Continuous Integration Diagram

When your team is small, or when there are too many things to have in mind, is easy to forget details, like invalidate the CloudFront cache after a deploy. You may end up never seeing your update in the live environment otherwise.

Or when that guy who does the deploys is sick and canโ€™t push the button, what do you do? Pospouse your production release?

To prevent all of this shenanigans, here is a CircleCI configuration that will deploy your frontend code to S3 and will invalidate your CloudFront cache after a merge to the desired branch.

CircleCI configuration file ๐Ÿ™Œ

version: 2
jobs:
"testing":
docker:
- image: circleci/node:10-stretch
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- app-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- app-
- run: npm install
- save_cache: # Save node_modules into cache with a checksum of the package.json
paths:
- node_modules
key: app-{{ checksum "package.json" }}
- run: npm run test # Run your tests
"deploy":
docker:
- image: circleci/node:10-stretch
working_directory: ~/repo
steps:
- checkout
- run:
name: Installing AWS CLI
working_directory: /
command: |
sudo apt-get -y -qq update
sudo apt-get install -y awscli
sudo apt-get install -y python-pip python-dev build-essential
sudo pip install awsebcli --upgrade
- run:
name: Preparing Artifact
command: |
npm install
npm run build # Here goes your build command.
cd dist # My angular app generate a Dist folder
zip ../build.zip -r * .[^.]* # Just zip your files
echo "Sucessfull building"
- run:
name: Deploying Client to S3 and Cloudfront
command: |
aws configure set preview.cloudfront true # Turn on cloudfront in AWS CLI
if [ "${CIRCLE_BRANCH}" == "production" ] # Check current branch to decide to which S3 bucket deploy
then
aws s3 sync ~/repo/dist s3://yoursite.com --delete
aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID_YOUR_SITE_PRODUCTION --paths /\*
elif [ "${CIRCLE_BRANCH}" == "staging" ]
then
aws s3 sync ~/repo/dist s3://staging.yoursite.com --delete
aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID_YOUR_SITE_STAGING --paths /\*
else
aws s3 sync ~/repo/dist s3://dev.yoursite.com --delete
aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID_YOUR_SITE_DEV --paths /\*
fi
workflows:
version: 2
build_and_deploy:
jobs:
- testing
- deploy:
requires:
- testing # Deploy require testing to be successful
filters:
branches:
only: # Only deploy for production, staging, and master branchs
- production
- staging
- master
view raw config.yml hosted with โค by GitHub

Explanation ๐Ÿฟ

Here we deploy our code to the AWS S3 bucket based on the target branch that was merged.

You can go all crazy here, and use semantic versioning, setting up regex rules to decide when a tag version is ready to publish to production or to beta.

The approach here is more classic.

You have your master branch where all code is merged, then the staging branch is where you merge master when is stable enough to be ready for QA manual revision.

Then production branch is merged with master after all acceptance tests were run and everybody is happy.

Run your Tests! ๐Ÿ‘ฎ

Please run your tests and make sure they pass before attempting any deployment. And remember to emit an error if your test fails so the deployment stops.

Look my friend, Iโ€™m not your manager you can skip this step but please think in your customers.

"testing":
  docker:
    - image: circleci/node:10-stretch
  working_directory: ~/repo
  steps:
    - checkout
    - restore_cache:
        keys:
        - app-{{ checksum "package.json" }}
        # fallback to using the latest cache if no exact match is found.
        - app-
    - run: npm install
    # Save node_modules into cache with a checksum of the package.json.
    - save_cache:
        paths:
          - node_modules
        key: app-{{ checksum "package.json" }}
    # PLEASE run your tests.
    - run: npm run test 

Prepare your artifact ๐Ÿ”ฎ

How you generate your artifact is entirely up to you.

In my case, Iโ€™m deploying an Angular2 application.

The npm run build generates a production build in the dist folder, so just by zipping that I was ready.

  - run:
    name: Preparing Artifact
    command: |
      npm install
      npm run build 
      cd dist       
      zip ../build.zip -r * .[^.]*
      echo "Artifact generated!"

The deploy to S3 bucket ๐Ÿ“ฆ

Here we are using a simple approach, checking what branch we are currently building on, and deciding to what S3 bucket we should deploy our code.

  if [ "${CIRCLE_BRANCH}" == "production" ]  # Check current branch to decide to which S3 bucket deploy.
  then
    # Aggressively replace your files
    aws s3 sync ~/repo/dist s3://yoursite.com --delete  

    # Invalidate Cloudfront Cache
    aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID_YOUR_SITE_PRODUCTION --paths /\* 
  elif [ "${CIRCLE_BRANCH}" == "staging" ]
  ...

The term โ€œdeployโ€ is used here as a fancy word for copy, pasting and replacing files

CloudFront Cache invalidation ๐Ÿ•ต๏ธโ€โ™‚๏ธ

The crucial detail โœจ

  aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID_YOUR_SITE_PRODUCTION --paths /\*

When you update your AWS S3 bucket files, AWS CloudFront will not care, because his job is to act as a cache layer.

You have to tell him that you want to โ€œcleanโ€ the โ€œcacheโ€.

Please tweak this as you please, maybe you want to exclude images cache from being refreshed.

Conclusion ๐ŸŽ‰

In this tutorial, we learned all the basic and necessary steps to create a continuous integration and continuous delivery solution for S3 and CloudFront by using the awesome CircleCI tool.

Archiving continuous integration is something that worths the time investment. It will pay you in a lot of time saved and human error prevention.

Please share this post with your coworkers!

Resources