Build image and push to private Github registry
Create a new workflow to build and push Docker images to GitHub Container Registry (GHCR) using BuildKit in a rootless
and daemonless mode. Create a new file in your repository at .github/workflows/build.yml with the following content:
Generate a Personal Access Token (PAT) with write:packages and read:packages permissions and add it to your
repository secrets as DEPLOY_TOKEN. This token will be used to authenticate with GHCR.
Add DEPLOY_TOKEN to your repository secrets by navigating
to Settings > Secrets and variables > Actions > New repository secret.
Add the following workflow to build and push Docker images to GHCR using BuildKit in a rootless and daemonless mode:
build-image:
runs-on: self-hosted
steps:
- uses: actions/checkout@v7
- name: Configure registry
run: |
mkdir -p /tmp/.docker
echo "{\"auths\":{\"ghcr.io\":{\"username\":\"${{ github.actor }}\",\"password\":\"${{ secrets.DEPLOY_TOKEN }}\"}}}" > /tmp/.docker/config.json
- name: Build
env:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
run: |
docker run --rm \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
-e BUILDKITD_FLAGS \
-v "$GITHUB_WORKSPACE":/workspace \
-v /tmp/.docker:/home/user/.docker:ro \
--entrypoint buildctl-daemonless.sh \
moby/buildkit:rootless \
build \
--frontend dockerfile.v0 \
--local context=/workspace \
--local dockerfile=/workspace \
--output type=image,name=ghcr.io/${{ github.repository }}:latest,push=true
Install a webhook on the server
On the serve, where the app is running install a small webhook server. This server is triggered from Github ci and deploys the app container.
apt-get update
apt-get install webhook
Check status of webhook
sudo systemctl status webhook
It will show inactive because You installed the Ubuntu/Debian webhook package, and its packaged
systemd service expects /etc/webhook.conf to exist.
sudo nano /etc/webhook.conf
For your deployment:
[
{
"id": "deploy",
"execute-command": "/opt/deploy.sh",
"command-working-directory": "/opt/myapp",
"trigger-rule": {
"match": {
"type": "value",
"value": "YOUR_SECRET_TOKEN",
"parameter": {
"source": "url",
"name": "token"
}
}
}
}
]
Then
sudo chmod 600 /etc/webhook.conf
sudo systemctl restart webhook
sudo systemctl status webhook
The webhook should be active at this point.
Create /opt/deploy.sh file with the instruction to pull image and deploy container.
#!/bin/bash
set -euo pipefail
cd /opt/myapp
echo "$GHCR_TOKEN" | docker login ghcr.io \
--username "$GHCR_USERNAME" \
--password-stdin
docker compose pull
docker compose up -d
docker logout ghcr.io
Make it executable
sudo chmod +x /opt/deploy.sh
Github credentials are supplied by an env file.
sudo mkdir -p /etc/myapp
sudo nano /etc/myapp/ghcr.env
Put the contentes in the /etc/myapp/ghcr.env file
GHCR_USERNAME=username
GHCR_TOKEN=github_pat_xxxxxxxxxxxxxxxxx
Create a directory
sudo mkdir -p /etc/systemd/system/webhook.service.d
Then create a file inside the directory
sudo nano /etc/systemd/system/webhook.service.d/override.conf
Put the content in the file. Make sure port 9000 is not allowed from the public internet in your VM firewall.
Important: Do not run this before enabling the firewall.
[Service]
ExecStart=
ExecStart=/usr/bin/webhook -hooks /etc/webhook.conf -verbose -ip 0.0.0.0 -port 9000
EnvironmentFile=/etc/portfolio/ghcr.env
Restart the webhook
sudo systemctl daemon-reload
sudo systemctl restart webhook
Now modify your routes.yaml file and add the service to connect to the webhook.
http:
routers:
portfolio:
rule: "Host(`yourdomain.com`)"
entryPoints:
- websecure
service: app
tls:
certResolver: letsencrypt
deploy-webhook:
rule: "Host(`yourdomain.com`) && PathPrefix(`/hooks/`)"
entryPoints:
- websecure
service: webhook-service
tls:
certResolver: letsencrypt
services:
app:
loadBalancer:
servers:
- url: "http://myapp:8000"
webhook-service:
loadBalancer:
servers:
- url: "http://host.docker.internal:9000"
Also add extra_hosts config in your Traefik config in the Docker compose file.
Without this it cannot reach the webhook running on local machine.
services:
traefik:
extra_hosts:
- "host.docker.internal:host-gateway"
After that update your CI file to trigger the webhook after an image is built.
This WEBHOOK_DEPLOY_TOKEN value is set as secret value from /etc/webhook.conf file.
trigger-webhook:
runs-on: self-hosted
container:
image: ubuntu@sha256:2edbbc5dc405e9612ba3584ce95480277e3eb374407b5505fe26f17df77c7dbc
needs: build-image
steps:
- name: Install curl
run: |
apt-get update
apt-get install -y --no-install-recommends curl ca-certificates
rm -rf /var/lib/apt/lists/*
- name: Trigger webhook
run: |
curl --fail-with-body --request POST \
--url "https://sunnychowdhury.dev/hooks/deploy" \
--header "Content-Type: application/json" \
--data '{"token":"${{ secrets.WEBHOOK_DEPLOY_TOKEN }}"}'