How to start using continuous integration in your web app ๐

Use CircleCI and automate your deploys for free ๐ข
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 implementing continuous integration? ๐ค
- The circleci config file ๐
- Explanation ๐ฟ
- Conclusion
Why continuous integration and delivery ? ๐ค
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? Postpone 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 |
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.
Resources
Get The Latest Articles In Your Inbox.
Join the other 2000+ savvy node.js developers who get article updates.
You will receive only high-quality articles about Node.js, Cloud Computing and Javascript front-end frameworks.
Unsubscribe anytime.