Setting up a Continuous Deployment pipeline with Docker and Travis
In the previous post we looked at a strategy for automating the deployment of single libraries/components.
In this entry I will describe how to setup a pipeline for a Continuous Deliver (CD) of an entire project, where every code commit which passes the CI test phase is automatically deployed into an environment, with changes that are immediately visible to the users.
This concept is a powerfull one, commiting your changes and seeing it built and deployed without any manual input is a huge time saver.
We will assume Travis CI is used for CI, Github for version control and Dockerhub as the Docker image repository. Obviously you will also need a Docker installation. Yuu should be able to adopt these steps for different settings/repositoreis used.
Let’s assume our project is shipped as a jar file, and needs a JRE environment to run.
In that case all we need is a following
FROM adoptopenjdk/openjdk8:latest MAINTAINER "Filip Bielejec" <nodrama.io> COPY target/app.jar . CMD ["java", "-jar", "app.jar"]
We start from an exisiting image that provides OpenJDK environment and copy the “app.jar” file, which is then executed in the CMD. We can test this build locally:
docker build -t nodrama/app -f docker-builds/Dockerfile .
Risking sounding opinionated I believe that Docker is to DevOps what GitHub is to development, which is why the right way to version Docker images is with the hashes corresponding to commits on the master branch.
If you follow the healthy practices of working with feature branches, squashing commits before merging them and never reverting commits on the master branch, you can immediately see the gains. You can track back running containers to the merged features, immediately roll-back containers in your environments to a known point (should something break), finally if they pass the QA phase you can deploy them to the production and be sure that no unknown bugs are shipped.
So how exactly do we tag the Docker images with commit hashes? We can get the hash of the last commit like this:
git log -1 => commit 7eba7eda97513e01f5421d05059cebcb861fd805
And we can build the image versioned with that hash like that:
docker build -t nodrama/app:7eba7eda97513e01f5421d05059cebcb861fd805 -f docker-builds/Dockerfile .
To automate the deployment we will always deploy the application image tagged as the latest. However we need to keep one thing in mind - Docker’s latest tag is just a meta-tag, not a semantic version, and means:
- either the last build ran without a specific version specified
- or the build that was tagged as such
Therefore the best practice is to explicitely tag the last built with the latest tag:
docker tag nodrama/app:7eba7eda97513e01f5421d05059cebcb861fd805 nodrama/app:latest
If we follow this practice, there should be no suprises, as the image tagged as the latest will always correspond to the last known build.
Travis readily supports building Docker images and pushing them to the Docker repositories.
To build and deploy an image of your app to Dockerhub add the following settings to the
.travis.yml file in the root of your projects repository:
services: - docker deploy: provider: script script: bash docker-push.sh on: branch: master
It simply says that every passing build on the
master branch run a deploy step using docker service and a custom deploy script.
To be able to authenticate against Dockerhub we need to add credentials to the Travis build environment.
Following command encrypts these credentials with the public key attached to your repository, and adds them to the
travis encrypt DOCKER_USERNAME=<dockerhub-username> --add travis encrypt DOCKER_PASSWORD=<dockerhub-password> --add
Now let’s look at the deploy script which builds and deploys the application image.
docker-push.sh script from the deploy step described above is a file in your repository with the following content:
NAME=nodrama/app TAG=$(git log -1 --pretty=%h) IMG=$NAME:$TAG # build jar mvn clean package -DskipTests # build and tag as latest docker build -t $IMG -f docker-builds/Dockerfile . docker tag $IMG $NAME:latest # dockerhub login echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin # push to dockerhub docker push $NAME
We have went through the steps to build and deploy our containerized applications image to the Docker repository. Now its time to automate updating of the running version of the app.
Watchtower is a service which automatically checks for, downloads and updates running containers. The best part is that Watchtower itself is a Docker container.
Unless you are really sure about your processes, CI, tests and coverage the “nightly builds” should rather be reserved for DEV or QA environments and not production. But the great thing about this process is that a vetted image (versioned with a commit tag) can be easily and quickly deployed to production.
Lets run a Docker container with our project:
docker run -d --name app nodrama/app:latest
Then we can start Watchtower:
docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock v2tec/watchtower:latest --cleanup --interval 300
Watchtower needs to interact with the Docker API in order to monitor the running containers, which is why we mount host volume
/var/run/docker.sock into the Watchtower container.
Cleanup means it will remove old images after updating.
We have also specified an interval of 300 seconds for checking for new image versions.
By default Watchtower will watch for and try to update all running containers.
For other configuration options you can consult the official documentation.
When it comes to the automated notifications I’m a big proponent of ChatOps. Chat messages, unlike emails, are consumable by the whole team and especially in the case of alerts there is simply a bigger chance of someone noticing and reacting on them.
Watchtower can send notifications through a Slack webhook when containers are updated. You simply need to pass two environment variables to it:
Here is a complete
docker-compose.yml file, which runs the app container and the Watchtower service:
app: image: nodrama/app:latest container_name: app watchtower: image: v2tec/watchtower:latest container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - WATCHTOWER_NOTIFICATIONS=slack - WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=https://hooks.slack.com/services/x/y/z command: --cleanup --interval 300
Thank you for reading!