ROOT CAUSE: All 3 runner labels pointed to code.forgejo.org/forgejo/runner:6
(bare Alpine). When Forgejo runs a workflow, it creates a container FROM the
label image — this container had no Node.js, no docker CLI, no Go/Rust,
no root access, and no apt-get. Every CI job failed.
FIX: Change runner labels to official Debian-based language images:
golang → docker://golang:1.26-bookworm (Go + apt-get + root)
node → docker://node:22-bookworm (Node.js + npm + apt-get + root)
rust → docker://rust:1-bookworm (Rust + cargo + apt-get + root)
The runner daemon pod still uses forgejo/runner:6 — only the label image
(what workflow steps execute in) changes.
Unified CI pattern for ALL repos:
1. Install Node.js first if not present (needed for actions/checkout@v4)
2. Install docker.io via apt-get (needed for docker build/push)
3. Use actions/checkout@v4 normally
4. Build/push with docker
IMPORTANT: Runners must re-register after merge. Delete PVCs or
/data/.runner files to trigger re-registration with new labels.
Problem: actions/checkout@v4 requires Node.js, but base forgejo/runner:6
(Alpine) doesn't have it. We need to test our Dockerfiles on the bare base image.
Solution:
- runs-on: golang (base Alpine runner with dind docker)
- Replace actions/checkout@v4 with git clone (no Node.js needed)
- Clone to /workspace, run all steps there
- Only push on push events (skip on PR to avoid registry pollution)
This validates that our Dockerfile fixes work correctly on base image.
Problem: Forgejo runner base image is Alpine Linux, not Debian.
- apt-get doesn't exist on Alpine (uses apk instead)
- Runner user (1000) can't modify apk database (Permission denied error)
- Workflow used GitHub-specific conditionals (contains() not Forgejo-compatible)
Solution:
1. Replace apt-get with apk add --no-cache for all runner Dockerfiles
2. Switch to USER root before package installation (apk needs root)
3. Switch back to USER 1000:1000 after install (security)
4. Simplify workflow: build all runners in loop (no conditionals)
Dockerfile changes:
- golang: +nodejs +npm +docker-cli via apk
- node: +nodejs +npm +docker-cli via apk
- rust: +nodejs +npm +curl +docker-cli via apk
Workflow trigger:
- Runs on any Dockerfile.* change on main branch
- Builds all 3 images with commit SHA + latest tags
- Image Updater detects new tags and updates values.yaml
After merge to main:
1. CI builds images: forgejo-runner-{golang,node,rust}:SHA
2. Images pushed to registry
3. Image Updater syncs images and commits values.yaml update
4. ArgoCD deploys new runner pods with docker available
- Removed .gitea/workflows/build-runner-node.yml
- Homelab is GitOps only, not a buildable artifact
- Runner images managed via direct Dockerfile edits + manual pushes
- Dockerfile.node extends node:22-bookworm + docker.io
- CI workflow builds and pushes to forgejo registry on changes
- values-node.yaml references custom image instead of stock node
- Removes need to install docker in every workflow using node runner