In this blog post I will show you how to implement angular 2 production deployment using angular-cli, Docker, NGinx with Continues Integration/Continues Delivery hosted on CircleCI.

So what's CI/CD is all about?

Continuous Integration (CI) is the practice of merging all developer working copies to a shared mainline several times a day.

Continuous Delivery (CD) is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time. It aims at building, testing, and releasing software faster and more frequently.

When thinking about implementing CI into your organization it's very important that your angular code will be tested (Unit tests and e2e test are highly recommended). Today your startup might have the PDG - Production Deployment Guru that knows every piece of the code and checks it before uploading the code manually to production servers. But with automated deployment, something needs to constantly check if your code is healthy, so if a programmer pushed a code that broke the build you will be notified and a fix could be made before it even reaches productions servers.

There are multiple tools on the market that help you with CI, they usually narrow down to 2 categories: Self-hosted and SaaS products.

A self-hosted solution is great for big teams and complex project set-ups. Also, for automated tests and automated build process it's usually necessary to pass environmental variables (private api keys and other sensitive information) to the build machine. When using the self-hosted solutions you own the entire stack, so no need to share your sensitive keys with third-party companies.

The major self-hosted solutions on the market for today are:

In this post I will focus on SaaS products and specifically implementing CI with CircleCI. After building a few projects with the competitors, we found that CircleCI offers great customization and ease of use. You can literally setup your project in a couple of minutes. The product seems very mature with a list of very big brands using it, it has great support for docker integrations and from my current experience builds are quite fast.

Some of the other SaaS products on the market:

Let's Start

You can find the final project on the github repository:
https://github.com/scopsy/angular-cli-circleci-docker

First we will start a new angular-cli project:

ng new ci-demo

The initial boilerplate already includes basic unit and integration testing. We will use them for our test-ci project. In this tutorial I assume that you will use the testing framework provided by the angular-cli team.

Let's start our project with ng serve and we'll see the famous "app works!" slogan.

DockerHub

In this tutorial I will use [Docker cloud] (https://cloud.docker.com) as a docker registry, you can of-course use any other provider you are currently using.

Register an account there and create your first repository.

CircleCI integration

First head over to CircleCI's website and create an account. I will not walk you through the registration process, it's really straight forward and the guys from circle did a great job with the on-boarding wizard. So go ahead, and return after you successfully connected your project.

circle.yml

CircleCI can be configured via their web interface, however in order to achieve maximum customization abilities it is recommended to create a circle.yml file, which is basically the configuration file circle will read after pulling your project from github.

Create a new file named circle.yml on the root of your project:


machine:
  services:
    # this tells circle that we want to include docker in our build deployment
    - docker
  node:
    # You can configure other node version here if needed
    version: 7.6.0
  # these are ENV variables the we can use on our build server
  # please note that sensitive variables will be passed on the web panel
  # Usually it's a good practice to keep your private keys away from source control
  environment:
      # the is the name of the repository your created in docker hub
      REPO: grossman/angular-cli-circleci

# General settings
general:
  # Circle ci allows you to save "artifacts" which are basically files you can access after the build finishes.
  # we gonna save our coverage test results
  artifacts:
    - "coverage"

# Dependencies
dependencies:
  pre:
    # we will login to dockerhub here please note the the docker credentials are coming from the environment
    # we will configure them in the web ui later
    - docker login -u $DOCKER_USER -p $DOCKER_PASS -e $DOCKER_EMAIL

    # Uncomment next section if you will implement SSL with nginx
    # since we don't want to give away our production ssl keys, we generate self signed test keys
    # needed only in the build ENV we will save them on the  home folder
    # - openssl req -x509 -nodes -newkey rsa:2048 -keyout ~/ci-test.key -out ~/ci-test.com.crt -subj "/C=NL/ST=Test SSL/L=Rotterdam/O=Testing Test/OU=IT Department/CN=circle.ci.demo"

# Test Commands
test:
  override:
    # Circle by default will run npm test command,
    # We want to override it so we can generate coverage as-well.
    - ng test --code-coverage --single-run

deployment:
  production:
    # Circle will listen to every tag submitted on github we use a regex to catch every semver tag starting with "v"
    # i.e "v1.0.0"
    tag: /v[0-9]+(\.[0-9]+)*/
    commands:
      - ng build -prod
      # here we build the docker container, the --rm=false is needed because some issue with
      # circle build leading to uncritical error during the build
      - docker build --rm=false -t $REPO:prod -f ./deployment/production/Dockerfile .
      # you can remove the -v and the second 443 port if you don't use SSL
      - docker run -d -i -v ~/:/etc/nginx/certs -p 80:80 -p 443:443 $REPO:prod
      # we want to test our newly built docker file to see if it's properly responding the static html
      - curl --retry 10 --retry-delay 5 -k -v https://localhost | grep '<app-root>'
      # docker image tagging, i will explain it in the article in mroe detail
      - docker tag $REPO:prod $REPO:$CIRCLE_TAG
      - docker push $REPO:prod
      - docker push $REPO:$CIRCLE_TAG
      # execute a deployment script
      - sh ./deployment/production/deploy.sh
  #staging:
    # here we can listen to every commit on the staging/qa branch
    # branch: "staging"
    # commands:
      # your qa build steps can be specified here

Deployment scripts

We will create a new folder in the root of the project called deployment and inside it we'll place two folders: staging and production

in the production folder we will create a deploy.sh file that will contain our deployment logic.
Feel free to transfer some logic the deployment block in circle.yml file to here. For this example we will stick with the current set-up.

#!/usr/bin/env bash

# This is the code that will update your server
sh {YOUR_SERVER_USER}@{YOUR_SERVER_IP} 'docker pull grossman/angular-cli-circleci:prod; docker stop circle-test; docker rm circle-test; docker run -d -i -v ~/ssl:/etc/nginx/certs --name circle-test -p 80:80 -p 443:443 grossman/angular-cli-cricleci:prod'

We connect to our production server, pulling latest prod build from dockerhub, stopping the current instance and running the new image with port mapping and attaching the ssl certificates volume to it.

Next, we will create the nginx.conf file in the same directory:

worker_processes 4;

events { worker_connections 1024; }

http {
  # Basic Settings
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;

  server {
    listen 80;
    server_name server.name;

    # redirect all calls to ssl
    return 301 https://$server_name$request_uri;
  }

  server {
    # if you don't use ssl remove the top server module
    # and change this ip to 80 and remove the ssl tag
    listen 443 ssl;

    server_name server.name;

    # if you don't use ssl comment this two lines
    ssl_certificate /etc/nginx/certs/ci-test.com.crt;
    ssl_certificate_key /etc/nginx/certs/ci-test.key;

    # Gzip Settings
    gzip on;
    gzip_http_version 1.1;
    gzip_min_length   1100;
    gzip_vary         on;
    gzip_proxied      expired no-cache no-store private auth;
    gzip_comp_level   9;

    root /usr/share/nginx/html;

    # Main file index.html
    location / {
      try_files $uri $uri/ /index.html =404;
      include /etc/nginx/mime.types;
    }
  }
}

And lastly we will create the Dockerfile containing all our container logic

# feel free to select here a specific nginx version to avoid future breaking changes.
FROM nginx:latest

COPY dist/ /usr/share/nginx/html
COPY deployment/production/nginx.conf /etc/nginx/nginx.conf

VOLUME /etc/nginx/certs

EXPOSE 80
EXPOSE 443

Great! we created our production configurations, feel free to create a similar configuration for your other environments (staging and etc...)

Docker tagging strategy

In this post we are using 4 central tags:

  • {repot-name}/latest - This is the most bleeding edge tag, created from every develop commit ( usually you will work on a sprint/feature branches and commit to develop only build-able and working code) Note: If your organization is small you can skip this branch hook.
  • {repot-name}/prod - This is the latest production code built from a Tagged release commit in github.
  • {repot-name}/staging - This tag will have the latest qa version of your app. Note: You can listen to every v0.0.0.rc-0 release tag to make a qa deployment, or you can listen to every commit on the staging branch.
  • {repo-name}/{git-tag} - for every production docker image we build we also create another tag with it's git release tag name. This is done in order to preserve your release history. So if bad code was deployed to production you can easily revert to the previous tagged version.

Setting ENV variables in CircleCI.

You may noticed we used some env variables like $DOCKER_EMAIL, also we are connecting via SSH to our server on the deploy.sh script.
To Add ENV variables visit your project settings page on CircleCI and then under "Build settings" go to "Environment Variables" page and add the next keys: DOCKER_EMAIL, DOCKER_PASS & DOCKER_USER. Feel free to add more variables if needed.
ssh-keys can be added on the SSH-permissions page.

Summary

CI/CD allows your team to release quickly, and when done with good tests less "buggy" versions since most of bugs and mistakes get caught when developer pushes code to the SC platform (You can even connect a nerf gun to shoot the rogue developer). Using this technique you can push ready code to production every day/multiple times a day. CI removes the hassle of manually updating your servers and automates the entire test/build process.

When combining CI with something like Beanstalk or other auto scaling management platforms you can get reliable and scalable infrastructure while maintaining team agility. And when the "deployment guru" goes on a vacation you can still publish code to your clients :)