Building a Pipeline to Deploy Docker Containers to a VPS
In this follow along, I want to show you a free and convenient way to automatically deploy new versions of your dockerized applications to a virtual private server with a simple git push
command.
We will use GitHub Actions to build the Docker image, store it in GitHub container registry (GHCR) and pull it on our VPS with Watchtower.
I will assume that you have access to a VPS already with Docker installed. If you are looking for a cheap VPS provider, I recommend you checkout the linked article. To install Docker, follow the instructions from their official website.
If you are just looking for a cheap way to deploy your containers, I recommend you checkout our service sliplane.io
Let's get started!
Create a Personal Access Token in GitHub
GitHub container registry (GHCR) is a free way to store private Docker images. To access it, we first need to create a personal access token.
- Go to the developer settings in GitHub
- Click Generate new token and choose Generate new token (classic).
- Give the token a descriptive name, set an expiration and select write:packages permissions. Generate the token and make sure to copy it as you'll only see it once.
You can find more detailed instructions in this article from Github.
Create a GitHub Actions Deploy Workflow
We can use GitHub Actions to build our Docker image and push it to GHCR. Inside the repository, that you want to deploy, create a file called release.yml inside .github/workflows.
.
βββ .github/
β βββ workflows/
β βββ release.yml
βββ ...
Copy the script below into the release.yml file and make sure to update the project-name in step 3 and 4. GitHub will automatically pickup and run this workflow on every new push to the main branch.
name: Build and Push Docker Image
on:
push:
branches:
- main
jobs:
build-and-push-to-ghcr:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.PAT }}
- name: Build Docker image
run: |
docker build -t ghcr.io/${{ github.repository_owner }}/project-name:latest .
- name: Push Docker image
run: |
docker push ghcr.io/${{ github.repository_owner }}/project-name:latest
Before you can run this script, you'll also have to store the personal access token in your repository. Go to your repo page, click on Settings > Secrets and variables > Actions, add a new secret named PAT and paste your previously created access token as a value.
Setup Watchtower
Watchtower is a service, that continuously pulls the underlying images from your running containers and compares them to the images in use. If the contents of a pulled image is different from the one in use, the container will be restarted using the new image.
SSH into your VPS:
ssh root@YOURVPSIP
Start Watchtower by simple running the Watchtower Docker container:
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e REPO_USER="your-github-username" \
-e REPO_PASS="your-github-pat" \
containrrr/watchtower \
--interval 300
Watchtower pulls information about running containers from the docker.sock
file, which will be mounted in a volume. By default all running containers are watched, but you can limit watching by passing specific container names:
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e REPO_USER="your-github-username" \
-e REPO_PASS="your-github-pat" \
containrrr/watchtower \
containername1 containername2 containername3 \
--interval 300
The REPO_USER
and REPO_PASS
environment variables can be used to authenticate when pulling from private registries. You can also execute docker login
to save your credentials in $HOME/.docker/config.json, and then mount this config file to provide auth credentials to the Watchtower container:
docker run -d \
--name watchtower \
-v $HOME/.docker/config.json:/config.json \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--interval 300
The --interval
flag specifies, how often new versions will be pulled, in this case every 300 seconds/ 5 minutes.
Watchtower can be pretty heavy on bandwidth. Make sure to fine tune it to only watch required containers and set the interval according to your needs.
If you want to deploy a repository for the first time, you need to manually pull and run the image once. Login to GHCR with
docker login ghcr.io -u USERNAME --password-stdin
and use the personal access token to authenticate.
Pull your image with
docker pull ghcr.io/NAMESPACE/IMAGE_NAME
And run it using
docker run IMAGENAME
Once it's running, watchtower will automatically refetch new versions from GHCR.
Summary
You can quickly create a deploy pipeline by following these steps:
- Setup a VPS and install Docker
- Create a PAT in GitHub to access the container registry
- Create a release Workflow that builds your docker image and publishes it on GHCR
- Run Watchtower to automatically sync new versions of your running containers with GHCR
How to improve this setup? Obviously this is just the starting point for your container deployment pipeline. You can improve this setup by adding a way to automatically add domain records, manage secrets, view logs and monitoring data, and much more... If you want to save yourself some hassle checkout our service sliplane.io which comes with all of these features out of the box.