This document provides a guide for setting up an automated pipe that tests an app, builds a docker container around it, pushes that container to a registry and then sshes in and updates the running application via docker-compose
.
Configure Server
Install Docker
See this guide for more instructions:
# uninstall conflicting packages
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
#install latest versions
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Create User to Run Service
Create the user whilst ensuring that we create their home dir (-m
) and that they have permission to use docker services (-G docker
):
sudo useradd -m -G docker username
As that user… Create docker-compose.yml
mkdir ~/project_name
touch ~/project_name/docker-compose.yml
- Typically best to version control this file
- Ensure that any secrets like environment variables are in a separate
.env
file and that file is.gitignore
ed to prevent sensitive info from being stored.
core:
env_file: .env.docker
image: us.gcr.io/some/namespace/image:latest
ports:
- 3000:3000
volumes:
...
depends_on:
...
Create update.sh
file
- Again we can version control this file
#!/bin/bash -e
COMPOSE_FILE=/home/username/project_name/docker-compose.yml
docker compose -f $COMPOSE_FILE pull servicename
docker compose -f $COMPOSE_FILE up -d servicename
Make sure that it is executable:
chmod +x ./updates.sh
Authenticate Docker Repos
If using normal docker registry:
docker login registry.example.com
Otherwise see Log in to Docker Repos for authenticating against google docker repo:
gcloud auth activate-service-account --key-file=/path/to/service_account.json
gcloud auth configure-docker
Create CI file in Github
Let’s create a new file in the project: .github/workflows/test_build_deploy.yaml
:
Initial Step and Tests
You probably want to put the deploy step behind some automated tests so that it doesn’t deploy if they fail.
We also set some env vars
name: Test, Build Container & Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF: ${{ github.ref }}
IMAGE: namespace/imagename
REGISTRY_HOSTNAME: us.gcr.io
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "20" # You can change this to your preferred Node.js version
- name: Install dependencies
run: npm ci
- name: Run Vitest
run: npm run test
Option A - Build and Push to GCR
Build a docker container and push to Google Container Registry.
build-and-push-to-gcr:
runs-on: ubuntu-latest
needs: test
permissions:
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v3
- uses: RafikFarhad/push-to-gcr-github-action@v5-rc1
with:
gcloud_service_key: ${{ secrets.GCLOUD_KEY }} # can be base64 encoded or plain text || not needed if you use google-github-actions/auth
registry: ${{ env.REGISTRY_HOSTNAME }}
project_id: cognivita
image_name: ${{ env.IMAGE }}
image_tag: latest,${{ github.ref_name }} # set image_tag to the tag name from the git repo
dockerfile: ./Dockerfile.worker
context: .
Option B - Build and Push to a Vanilla Docker Registry
See:
- build-push-action for options
- docker/login-action for auth options for different types of registries
docker:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: user/app:latest
Deploy Step
Use SSH to log into the prod machine and have it pull the newly created docker image(s):
deploy-new-containers:
runs-on: ubuntu-latest
needs:
- build-and-push-to-gcr
steps:
- name: executing remote ssh commands using ssh key
uses: appleboy/[email protected]
with:
host: ${{ secrets.DEPLOY_SSH_HOST }}
username: ${{ secrets.DEPLOY_SSH_USERNAME }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: /home/path/to/update.sh
Prepare Git Secrets
SSH
- Add the IP address of the host machine under
DEPLOY_SSH_HOST
- Add the username of the user who will run the script under
DEPLOY_SSH_USERNAME
Generate the SSH key for the user:
ssh-keygen -f example
Copy ssh public key to remote machine
ssh-copy-id -i example user@machine
Copy the contents of the private key and add to repo under DEPLOY_SSH_KEY
Docker Secrets for Google GCR
If using Google, generate a service account with Artifact Registry Writer
permission (see this list for more info). Add a JSON key to the service account and download it. Then base64 encode it:
base64 keyname.json
Copy the output and add it to the repo as a secret called GCLOUD_KEY
Docker secrets for non-GCR Repo
Depending on which registry you are using (See docker/login-action) add:
DOCKERHUB_USERNAME
- to push to dockerhub registry withDOCKERHUB_TOKEN
- application token to push to registry with
Checklist
Server Initial Config
- Create server VM
- Install docker
- Create service user (and grant docker permissions)
- Create project folder
- Create docker-compose.yaml
- Create update.sh
- Authenticate against docker repo
CI File
- Add CI file
- Add testing step
- Add build step (either using google or docker registry)
- Add deploy step
Github Secrets
- Add Env Vars:
-
DEPLOY_SSH_HOST
-
DEPLOY_SSH_USERNAME
-
DEPLOY_SSH_KEY
- Docker Auth:
-
DOCKERHUB_USERNAME
-
DOCKERHUB_TOKEN
-
- or
-
GCLOUD_KEY
-
-
Final Checks
- update.sh executable
- Path to update.sh on server is correct in ci file
- container repo name is correct in CI file and docker-compose file.
- all env vars are set up in git
- server is authenticated and can pull docker images from repo