No description
  • Dockerfile 95.2%
  • JavaScript 4.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
blenderwizard 812bc0df44
All checks were successful
Build and publish images / publish (push) Successful in 4m55s
Update environments/node.Dockerfile
2026-11-02 10:11:20 +01:00
.forgejo Update renovate/renovate Docker tag to v44.199.2 2026-11-02 03:18:21 +00:00
environments Update environments/node.Dockerfile 2026-11-02 10:11:20 +01:00
.dockerignore Worker Images 2026-10-09 19:35:22 +02:00
.gitignore Worker Images 2026-10-09 19:35:22 +02:00
Dockerfile Update Dockerfile 2026-11-02 10:04:23 +01:00
README.md Worker Images 2026-10-09 19:35:22 +02:00
renovate.json Worker Images 2026-10-09 19:43:32 +02:00
versions.env Update Node.js to v26 2026-10-29 10:35:59 +01:00

worker-image

Docker images for Forgejo Actions CI. Every image is Debian-based and ships Node.js (managed by nvm) plus git, curl and ca-certificates, so actions/checkout and other Node-based actions work out of the box. On top of that base, one tagged image per language environment is published.

Tags

Tag Contents (in addition to the base image)
latest The base "blank" image: Debian trixie + Node.js 24 (nvm) + git/curl
c-cpp gcc/g++, make, CMake 4.4.4 (official binary release), ninja, gdb, pkg-config, valgrind
rust rustup, stable toolchain, rustfmt, clippy
rust-nightly rustup, nightly toolchain, rustfmt, clippy
go Go 1.27.2 (GOPATH=/go, $GOPATH/bin on PATH)
lua Lua 5.4, Lua development headers, luarocks
python Python 3, venv support, uv and uvx
node yarn (via corepack, latest stable yarn 4)
zig Zig 0.17.0 (/opt/zig)
gradle OpenJDK 21, Gradle 9.8.1
maven OpenJDK 21, Maven 3.9.16
ruby Ruby 4.0.7, RubyGems, bundler (from the official ruby image)

The default Node.js version (24 LTS) is installed through nvm and exposed on PATH for all shells. The nvm installation itself lives in /usr/local/nvm, so a job can switch versions at runtime:

- name: Use Node 22
  shell: bash
  run: |
    source /usr/local/nvm/nvm.sh
    nvm install 22
    nvm use 22
    node --version

Note that nvm is a bash function, so such steps need shell: bash. Steps that just use the default version work in any shell.

Using the images

Reference the image from your Forgejo instance's container registry:

name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: git.example.com/your-user/worker-image:rust
    steps:
      - uses: actions/checkout@v4
      - run: cargo test --all

The registry path is <your-forgejo-host>/<owner>/<repo where the images are published>. The publishing workflow (below) derives this automatically from the repository it lives in.

Publishing

.forgejo/workflows/publish.yaml builds and pushes all ten images to the Forgejo container registry on every push to master (and via manual workflow_dispatch).

How it works:

  1. Builds the base image from Dockerfile and pushes it as :latest. A local :blank tag is kept for the next step but not published.
  2. Builds each environments/<name>.Dockerfile with --build-arg BASE_IMAGE=<image>:blank (the local tag from step 1) and pushes it as :<name>.

Requirements

  • A runner with a working Docker daemon. See Sample CI runner for a ready-to-use setup, or any other runner whose daemon the job can reach.
  • The runner must be able to fetch actions/checkout (directly from GitHub or through a mirror configured on your instance).

Authentication

The workflow logs in with ${{ github.actor }} and ${{ secrets.REGISTRY_TOKEN || secrets.GITHUB_TOKEN }}:

  • If your instance grants the built-in GITHUB_TOKEN write access to packages, no extra setup is needed.
  • Otherwise create a Forgejo access token with write access to packages and store it as the REGISTRY_TOKEN secret on this repository. It takes precedence over the built-in token.

Sample CI runner (docker compose with dind)

compose/ contains a sample docker compose setup that runs a Forgejo runner with its own isolated Docker daemon (docker-in-docker), suitable for running the publish workflow:

compose/
├── compose.yaml        docker:dind + forgejo runner
└── runner-config.yml   runner configuration (edit this)

Setup:

  1. Edit compose/runner-config.yml:

    • server.connections: on Forgejo instances with the Create new runner dialog (Settings -> Actions -> Runners), copy the offered block (url, uuid, token) into it. On instances without that dialog, leave server.connections empty and register once instead:

      cd compose
      docker compose run --rm forgejo-runner forgejo-runner register \
        --config /config.yml \
        --instance https://forgejo.example.com \
        --token <registration-token> \
        --name worker-runner \
        --labels 'ubuntu:docker://ghcr.io/catthehacker/ubuntu:act-latest'
      
    • runner.labels already covers the runs-on: ubuntu used by the publish workflow. The label image (catthehacker/ubuntu:act-latest) ships bash and the Docker CLI, which the job needs to run docker build/docker push.

  2. Start it:

    cd compose
    docker compose up -d
    

How docker access works (all of this is already configured):

  • The runner starts every job, service and step container on the dind daemon (container.docker_host), never on the host daemon.
  • Every job container receives DOCKER_HOST=tcp://docker-in-docker.docker.internal:2375 (runner.envs), so docker CLI steps inside jobs reach the same dind daemon. The hostname resolves inside dind via the --add-host=...:host-gateway entry in container.options.
  • dind publishes no ports to the host; only the runner and its job containers can reach it. Images built by jobs are kept in the named dind-storage volume — prune it occasionally (docker compose exec docker-in-docker docker system prune -af).

Keeping images up to date (Renovate)

auto-update/ contains a self-hosted Renovate setup that tracks every pinned version in the Dockerfiles and opens PRs against this repository. Merging a PR pushes to master, which triggers the publish workflow — the PR is the update.

File Purpose
auto-update/run.sh Wrapper: loads .env, resolves the renovate CLI (nvm-aware), runs it
auto-update/config.json Bot config: Forgejo endpoint, regex managers per pinned ARG, package rules
auto-update/.env.example Template for the git-ignored .env holding the token

Setup (full details in the run.sh header):

  1. Node.js >= 20 on the host, then npm install -g renovate@44.
  2. Create a Forgejo PAT for a bot user with write access to this repo (scopes: repo r/w, issue r/w, user read, organization read), put it in auto-update/.env as RENOVATE_TOKEN (copy from .env.example).
  3. Test without creating PRs: ./auto-update/run.sh --dry-run=full.
  4. Schedule it, e.g. crontab: 0 4 * * 1 /path/to/worker-image/auto-update/run.sh >> /path/to/worker-image/auto-update/renovate.log 2>&1

What it tracks:

  • The *_VERSION ARG pins via regex managers: nvm, Node.js (github-tags), CMake (github-tags), Go (golang-version), Zig (git-tags on Codeberg), Gradle (gradle-version), Maven (maven-version).
  • debian:trixie-slim and the ruby:*-slim-trixie image tag via the built-in dockerfile manager (the ruby tag updates the RUBY_VERSION ARG).
  • Patch releases automerge; minor and major bumps are regular PRs.
  • Distro jumps off trixie are blocked (allowedVersions), since those need a deliberate rebuild.

Apt-installed toolchain pieces (gcc, OpenJDK, valgrind, lua, ...) refresh on every rebuild; run the publish workflow's workflow_dispatch now and then to pick those up without any version change.

Building locally

# Base image
docker build -t worker-image:blank .

# Any environment (defaults to BASE_IMAGE=worker-image:blank, so the base
# must be built/tagged first; CI overrides this with the registry-qualified tag)
docker build --tag worker-image:rust -f environments/rust.Dockerfile .

Adding or changing environments

  • New environment: add environments/<name>.Dockerfile starting with ARG BASE_IMAGE / FROM ${BASE_IMAGE}, then add <name> to the loop in .forgejo/workflows/publish.yaml and to the tag table above.
  • Tool versions are pinned as build arguments, so bumping is one line:
    • Dockerfile: NVM_VERSION, NODE_VERSION
    • environments/c-cpp.Dockerfile: CMAKE_VERSION
    • environments/go.Dockerfile: GO_VERSION
    • environments/zig.Dockerfile: ZIG_VERSION
    • environments/gradle.Dockerfile: GRADLE_VERSION
    • environments/maven.Dockerfile: MAVEN_VERSION
    • environments/ruby.Dockerfile: RUBY_VERSION (official ruby:*-slim-trixie image tag)

Notes

  • Rust and rust-nightly install toolchains into /usr/local/rustup and /usr/local/cargo, shared across users of the image via PATH.
  • The Go image does not include a C compiler; set CGO_ENABLED=0 or use the c-cpp image if your build needs cgo.
  • Images are built for the runner's native architecture. The base, go, zig, rust, gradle, maven, node, lua and python images work on both amd64 and arm64 runners; multi-arch manifest publishing is not set up.