Automating Docker Image Builds and Deployment with GitHub Actions and Watchtower

Raphael Derouelle
5 min readJun 10, 2024

--

Pipeline diagram

In this tutorial, we’ll walk through setting up a CI/CD pipeline for a simple Node.js application using GitHub Actions to build and push Docker images to a registry, and configuring Watchtower to automatically deploy these images.

This tutorial is designed for small personal pipelines and provides a straightforward way to automate Docker image builds and deployments using GitHub Actions and Watchtower.

1 — Run a Simple Node.js Application manually using docker

First, fork the repository from GitHub and create your own repository on which you will push the Node.js application.

  • Navigate to the repository on GitHub.
  • Click on the “Fork” button in the top-right corner to create your own copy of the repository.
  • Clone your forked repository to your local machine:
git clone https://github.com/yourusername/tutorial-docker-actions-watchtower
cd tutorial-docker-actions-watchtower

For this tutorial, we will use Docker Hub, but you can use any other registry (even private ones). The repository will remain public, but you can also make it private and use access tokens for GitHub Actions to push new images and for WatchTower to pull them.

Ensure you have Docker installed and running on your machine and already have a Docker Hub Account.

Login to Docker Hub using this command and provide your username and password (For better security you can also login using a limited-privilege personal access token that you create on your docker hub account).

docker login

Build the Docker Image.

❗️ Change “username” with your docker hub username.

docker build -t username/app .

Once the image is built, let’s test it by running a container !

docker run -d --name app -p 3000:3000 username/app

When your container is running, open http://localhost:3000 in you web browser. The application should be running.

Now that we’ve tested our app, we will push the image to the registry.

docker push username/app

2 — Set Up Github Actions workflow

Create a .github/workflows directory in your project and add a workflow file docker-build-push.yml this file will hold all of the instruction to login into the repository, access token, build and push.

name: Image Build and Push

on:
push:
branches:
- main

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
buildkitd-flags: --debug

- name: Login to Docker registry
uses: docker/login-action@v2
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: raphaelderouelle/app:latest

We then have to create an access token on docker hub in `Account Settings > Security > Access Tokens`.

⚠️ Copy your access token and store it in a safe place, you won’t be able to access it again.

Next, hop back on your GitHub repository, navigate to Settings > Secrets and variables > Actions and click on “New repository secret”.

Create 2 secrets :

  • REGISTRY_USERNAME with the secret being your docker hub username.
  • REGISTRY_PASSWORD with the secret being the access token.

These secrets are used by the workflow file in order to login into your docker hub account.

3 — Setup Watch Tower

Watchtower is a tool to automatically update Docker containers.

This command will create a container for watchtower, we tell him to only watch our “app” container.

The --interval tag is the interval between registry updates (in seconds). In our case, I set it to 2 min.

The --cleanup tag exists so that the previous images versions are deleted from your machine.

docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower my-node-app \
--interval 120 \
--cleanup

You can check that it’s running properly using logs.

docker logs watchtower

4 — Test the deployment pipeline

Modify public/index.html to reflect the new version:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deployment Success</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>You've Successfully Deployed Version 1.0.1 ✅</h1>
</div>
</body>
</html>

Commit your changes and push the changes to GitHub:

git add .
git commit -m "Update version to 1.0.1"
git push origin main

You will be able to watch the GitHub actions workflow status inside the Actions tab on your repository.

At the end of the workflow, the latest image should be available on your Docker Hub registry.

WatchTower will automatically deploy the new image. We can check the logs again.

level=info msg="Note that the first check will be performed in 1 minute, 59 seconds"
level=info msg="Session done" Failed=0 Scanned=1 Updated=0 notify=no
level=info msg="Found new raphaelderouelle/app:latest image (17b2965cdce3)"
level=info msg="Stopping /app (0d65067b46e1) with SIGTERM"
level=info msg="Creating /app"
level=info msg="Removing image 98ee58a66697"
level=info msg="Session done" Failed=0 Scanned=1 Updated=1 notify=no

As we can see, our “app” container was updated. Now let’s open our browser to check.

Conclusion

You’ve set up a CI/CD pipeline using GitHub Actions to build and push Docker images to a registry and configured Watchtower to automatically update your running container at specified intervals. This ensures that any changes pushed to your GitHub repository are automatically deployed, providing a seamless workflow for continuous integration and deployment of personal applications.

ℹ️ This tutorial is designed for small personal pipelines and provides a straightforward way to automate Docker image builds and deployments using GitHub Actions and Watchtower. However, in an organizational setting, you would typically use Kubernetes with sophisticated deployment strategies to avoid downtime during updates. Tools like ArgoCD or FluxCD (GitOps) would be employed to sync your repository and manage the deployment process seamlessly. Additionally, it’s important to note that Watchtower has limitations, such as primarily supporting the “latest” tag, which might not be suitable for more complex versioning requirements.

--

--

Raphael Derouelle

I am a Junior DevOps Engineer, I enjoy sharing knowledge !