Modern WebApps, build with React or Angular2 for example, are setup with Node.js, Less or Sass and a lot of npm packages. Those apps require a build step before you deploy it on a webserver.

In an effort to fully automate the deployment flow of a WebApp, I’ve hacked this recipe together with:

This recipe eliminates all manual steps to build and deploy WebApps. The only thing you need to do is to push to your master branch, a few minutes later the app will be deployed.

Build vs. production container

An image that contains Node.js, npm packages, etc can be quite big. You don’t won’t a container of 1GB in production for just hosting static content.

The recipe contains 2 Docker images:

A build image: image contains all dependencies to develop & build a WebApp

A production image: simple image, based on Linux Alpine, that contains the Nginx webserver

The solution

The development image creates an unique artifact (a tar.giz file) and uploads it to S3. The production image downloads and extracts the artifact in the Nginx data folder.

Build image

The build image installs all the npm packages and builds the WebApp. The important part of this this image is that it contains an entrypoint to create a tar.gz file of the compiled WebApp and uploads it to S3.

Entrypoint snippet:

case "$1" in

bash)

/bin/bash "${@:2}"

;;

create)

commit_hash=$( cat /commit_hash.txt | cut -c -8)

tar -zcvf app_$commit_hash.tar.gz dist

;;

upload)

commit_hash=$( cat /commit_hash.txt | cut -c -8)

# Create the bucket if it doesn't exist

aws s3api create-bucket --bucket ${@:2}

aws s3 cp app_$commit_hash.tar.gz s3://${@:2}/ --acl public-read

;;

esac

The entrypoint uses the latest commit hash, which is added by the Dockerfile, to add an unique version to the tar.gz file.

Here’s the trick, the following 2 lines are added at the bottom of the image:

RUN /usr/local/bin/docker-entrypoint.sh create

RUN /usr/local/bin/docker-entrypoint.sh upload docker-tmp-release

This will create and upload the artifact to a S3 bucket docker-tmp-release (replace this with a bucket name of your own account).

It’s kind of ugly to upload a file during the build process of the docker image. But this allows you to use the automated builds feature of docker hub to build and upload the artifact when you push to the master branch.

Production image

The production image is being build with the automated builds feature of docker hub as well.

Snippet that downloads and extracts the artifact:



&& cd /var/www \

&& ( \

commit_hash=$( cat /commit_hash.txt | cut -c -8); \

curl -O

tar -xvzf /var/www/app_$commit_hash.tar.gz; \

mv dist webapp; \

rm app_$commit_hash.tar.gz; \

date > webapp/version.txt; \

echo $commit_hash >> webapp/version.txt; \

) RUN mkdir -p /var/www \&& cd /var/www \&& ( \commit_hash=$( cat /commit_hash.txt | cut -c -8); \curl -O https://s3.amazonaws.com/docker-tmp-release/app_$commit_hash.tar.gz; tar -xvzf /var/www/app_$commit_hash.tar.gz; \mv dist webapp; \rm app_$commit_hash.tar.gz; \date > webapp/version.txt; \echo $commit_hash >> webapp/version.txt; \

Automated builds

You need to link your Github or Bitbucket repository with Docker hub and create automated builds.

For the production image you need to switch off the When active, builds will happen automatically on pushes option.

Add the build image as repository link to the production image. When the build image is updated it will automatically trigger a rebuild of this Automated Build.

Docker cloud

Docker cloud can be configured to automatically redeploy a container once an image has been updated. Set autoredeploy: true

Example stack file:

app:

image: lukin0110/webapp:latest

autoredeploy: true

ports:

- "80:80"

privileged: false

restart: always

target_num_containers: 1

deployment_strategy: emptiest_node

S3 Bucket

Add your AWS credentials in the credentials file. Make sure they have access to S3.

In both containers you need to replace the bucket name docker-tmp-release to another bucket name. All bucket names on S3 are unique.

The full source code is available on Github.