Building Docker images with Forgejo Actions
Lately I was looking for a Docker registry that I could host locally and keep couple of images that I run on multiple hosts. I didn’t want to run a bare registry but something more complete and I settled on a Forgejo instance which will also make me able to keep Dockerfiles and use it’s integrated CI to build the images.
Forgejo’s CI is based on GitHub Actions (unfortunately - I’d love to see an option to use GitLab CI workflow and syntax instead) so it’s easy to find workflows that can be adapted for your needs. The setup of a runner is not that straightforward, though - Forgejo’s docs are a bit vague so I’ll present a minimal configuration that I got to work properly. For security reasons, I chose to build images with buildah since kaniko is no longer maintained .
I deployed my runner with Docker Compose on an Alpine Linux host:
volumes:
runner-data:
services:
forgejo-runner:
image: data.forgejo.org/forgejo/runner:13
container_name: forgejo-runner
restart: unless-stopped
command: forgejo-runner daemon --config /data/runner-config.yml
group_add:
- "102" # docker socket host group, obtained with "stat -c '%g' /var/run/docker.sock"
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /etc/ssl/certs:/etc/ssl/certs:ro
- runner-data:/data
- ./runner-config.yml:/data/runner-config.yml
- ./seccomp.json:/data/seccomp.json:ro
- /var/run/docker.sock:/var/run/docker.sock
Tricky part is to set proper group in group_add and mounting a seccomp.json file. The latter contains a default seccomp file (which you can get from https://github.com/moby/profiles/blob/main/seccomp/default.json
) with a set of extra rules added to syscalls dict:
{
"names": ["unshare"],
"action": "SCMP_ACT_ALLOW",
"comment": "Allow unshare for buildah"
},
{
"names": ["mount"],
"action": "SCMP_ACT_ALLOW",
"comment": "Allow mount for buildah"
},
{
"names": ["umount2"],
"action": "SCMP_ACT_ALLOW",
"comment": "Allow umount2 for buildah"
}
I found these rules on David Robillard ’s blog - there’s no way I would come up with them on my own, at least not without extensive digging into seccomp.
The last piece of configuration needed is loading this modified seccomp profile in Forgejo runner’s config:
container:
options:
"--security-opt seccomp=/data/seccomp.json"
David’s post also mentions modifying AppArmor profile, but my Alpine host does not have AppArmor installed.
After applying the above configuration, a working workflow looks like this one:
name: build
on:
push:
branches:
- master
schedule:
- cron: "0 8 * * 0" # each monday
timezone: Europe/Warsaw
jobs:
build:
runs-on: docker
container:
image: git.example.com/adam/buildah-node:latest
env:
STORAGE_DRIVER: vfs
IMAGE_REGISTRY: git.example.com
IMAGE_NAME: adam/homelab-traefik
IMAGE_TAG: v3
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Login to Forgejo Container Registry
run: echo "${{ secrets.TOKEN }}" | buildah login -u "${{ forgejo.actor }}" --password-stdin "${IMAGE_REGISTRY}"
- name: Build and push
run: |
buildah bud -t "${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" .
buildah push "${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
For building, I use a custom buildah-node image that is based on official Buildah image with nodejs, git and my custom CA cert installed on top:
FROM quay.io/buildah/stable
RUN dnf install -y nodejs git && dnf clean all
COPY my-ca.crt /etc/pki/ca-trust/source/anchors/
RUN update-ca-trust
Keep in mind that this is a minimal configuration and probably can be additionally hardened. My Forgejo installation is not exposed to the world and I do not intend to use unverified CI workflows so this is good enough for me.