- Shell 66.8%
- Go Template 24.3%
- Dockerfile 8.9%
| templates | ||
| .dockerignore | ||
| Chart.yaml | ||
| Dockerfile | ||
| entrypoint.sh | ||
| forgejo-runner-dind-0.1.0.tgz | ||
| index.yaml | ||
| README.md | ||
| values.yaml | ||
Forgejo Runner DinD
Custom container image and Helm chart for running Forgejo Actions runners on Kubernetes with rootful Docker-in-Docker support.
Overview
This project provides:
- Custom container image combining Forgejo Runner with rootful Docker-in-Dind (DinD) in a single container.
- Helm chart for deploying the runner as a Kubernetes Deployment.
The image is designed for docker build / docker push workflows where the runner needs its own Docker daemon.
Architecture
┌─────────────────────────────────────────────┐
│ Kubernetes Pod (privileged) │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Custom Image: forgejo-runner-dind │ │
│ │ │ │
│ │ ┌─────────────┐ ┌────────────────┐ │ │
│ │ │ forgejo- │ │ dockerd │ │ │
│ │ │ runner │ │ (rootful DinD) │ │ │
│ │ │ daemon │ │ │ │ │
│ │ │ │ │ :2375 (127.0.0.1)│ │ │
│ │ └─────────────┘ └────────────────┘ │ │
│ │ │ │
│ │ DOCKER_HOST=tcp://127.0.0.1:2375 │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Components
| Component | Source |
|---|---|
forgejo-runner |
Official release (v12.7.3) |
| Docker daemon | docker:27-dind |
| Runtime | tini (init system) |
| Tools | git, bash, curl, jq, coreutils, ca-certificates |
Container Image
Building
docker build -t git.home-net.work/sjenkins/forgejo-runner-dind:12.7.3 .
Base image
- Base:
docker:27-dind
The image extends docker:27-dind with the forgejo-runner binary downloaded directly from the official releases.
Entrypoint
The entrypoint script (entrypoint.sh):
- Starts
dockerdontcp://127.0.0.1:2375with the configured storage driver - Waits for Docker daemon readiness
- Registers the runner with Forgejo (if not already registered)
- Starts
forgejo-runner daemon
Helm Chart
Prerequisites
- Kubernetes 1.25+
- Helm 3.x
- A Forgejo instance with Actions enabled
- A runner registration token from your Forgejo instance
Installation
Using an existing secret
helm install forgejo-runner ./ \
--set forgejo.instanceUrl=https://git.home-net.work \
--set forgejo.existingSecret=my-forgejo-secret \
--set runner.labels="docker:docker://node:20-bookworm" \
--set image.repository=git.home-net.work/sjenkins/forgejo-runner-dind \
--set image.tag=12.7.3 \
--namespace forgejo-runners \
--create-namespace
Using chart-generated secret
helm install forgejo-runner ./ \
--set forgejo.instanceUrl=https://git.home-net.work \
--set forgejo.registrationToken="your-registration-token-here" \
--set runner.labels="docker:docker://node:20-bookworm" \
--set image.repository=git.home-net.work/sjenkins/forgejo-runner-dind \
--set image.tag=12.7.3 \
--namespace forgejo-runners \
--create-namespace
Configuration
See values.yaml for all available options. Key configuration:
| Parameter | Description | Default |
|---|---|---|
image.repository |
Container image repository | git.home-net.work/sjenkins/forgejo-runner-dind |
image.tag |
Container image tag | 12.7.3 |
forgejo.instanceUrl |
Forgejo instance URL | https://git.home-net.work |
forgejo.existingSecret |
Existing secret name for registration token | "" |
forgejo.registrationToken |
Registration token (if no existing secret) | "" |
runner.labels |
Runner labels for job matching | docker:docker://node:20-bookworm |
runner.name |
Runner name (empty = pod hostname) | "" |
docker.storageDriver |
Docker storage driver | overlay2 |
persistence.docker.enabled |
Use PVC for Docker graph | false |
persistence.runner.enabled |
Use PVC for runner data | false |
nodeSelector |
Node selector for pod placement | {} |
tolerations |
Tolerations for pod placement | [] |
Example workflow
name: build
on:
push:
branches:
- main
jobs:
docker-build:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Docker info
run: docker info
- name: Login
run: echo "$REGISTRY_PASSWORD" | docker login git.home-net.work -u "$REGISTRY_USERNAME" --password-stdin
- name: Build
run: docker build -t git.home-net.work/sjenkins/example:latest .
- name: Push
run: docker push git.home-net.work/sjenkins/example:latest
Docker access in job containers
The runner config uses a two-part approach for DinD in a single container:
container:
docker_host: tcp://127.0.0.1:2375 # runner connects to Docker locally
privileged: true
options: "--add-host=host-gateway:host-gateway" # DNS for job containers
runner:
envs:
DOCKER_HOST: tcp://host-gateway:2375 # passed into job containers
container.docker_host: tcp://127.0.0.1:2375— the runner process connects to Docker on its own loopbackrunner.envs.DOCKER_HOST: tcp://host-gateway:2375— job containers reach the DinD daemon via Docker'shost-gatewayspecial DNSoptions: --add-host=host-gateway:host-gateway— ensureshost-gatewayresolves inside job containers
Why not
automount? In DinD, the Docker socket is inside the container, not on the host.automountonly works when the socket is on the actual host filesystem. Why nottcp://127.0.0.1:2375for job containers?127.0.0.1in a job container points to itself, not the runner pod.
Security considerations
- Privileged pods: The container runs with
privileged: truefor rootful DinD. - Docker daemon: Bound to
127.0.0.1:2375, not exposed externally. - TLS disabled:
DOCKER_TLS_CERTDIR=""disables DinD TLS (set a directory to enable). - Dedicated namespace: Recommended to isolate runners in a separate namespace.
- Dedicated nodes: Use
nodeSelector/tolerationsto pin runners to worker nodes. - Ephemeral storage: By default uses
emptyDir; use PVC-backed/var/lib/dockerfor large builds.
Troubleshooting
Runner not picking up jobs
- Verify labels match:
kubectl exec <pod> -- forgejo-runner list-labels - Check registration:
kubectl exec <pod> -- cat /data/.runner - Verify Forgejo instance URL:
kubectl exec <pod> -- env | grep FORGEJO
Docker not ready
- Check dockerd logs:
kubectl logs <pod> - Verify storage driver:
kubectl exec <pod> -- docker info | grep Storage - Check node supports overlay2:
kubectl exec --privileged <node-pod> -- modprobe overlay
Jobs fail with "Cannot connect to the Docker daemon"
Verify the config has both parts:
container:
docker_host: tcp://127.0.0.1:2375
options: "--add-host=host-gateway:host-gateway"
runner:
envs:
DOCKER_HOST: tcp://host-gateway:2375
- Check the runner can reach Docker:
kubectl exec <pod> -- docker info - Check job container env:
kubectl exec <pod> -- cat /data/config.yaml | grep DOCKER_HOST 127.0.0.1in a job container = itself,host-gateway= the pod's network interface
Consuming the Chart
The chart is published to the Forgejo Helm package registry.
Add the repository
helm repo add forgejo-runner-dind --username <user> --password <token> \
https://git.home-net.work/api/packages/sjenkins/helm
Update and install
helm repo update
helm search repo forgejo-runner-dind
helm install forgejo-runner forgejo-runner-dind/forgejo-runner-dind \
--set forgejo.instanceUrl=https://git.home-net.work \
--set forgejo.registrationToken=<token> \
--namespace forgejo-runners \
--create-namespace
Repository Structure
.
├── Chart.yaml # Helm chart metadata
├── Dockerfile # Multi-stage Docker image definition
├── entrypoint.sh # Container entrypoint script
├── values.yaml # Helm chart default values
├── templates/
│ ├── _helpers.tpl # Helm template helpers
│ ├── deployment.yaml # Kubernetes Deployment
│ ├── secret.yaml # Registration token secret
│ ├── configmap.yaml # Optional ConfigMap
│ └── serviceaccount.yaml # Optional ServiceAccount
└── README.md