- Dockerfile 95.2%
- JavaScript 4.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Build and publish images / publish (push) Successful in 4m55s
|
||
| .forgejo | ||
| environments | ||
| .dockerignore | ||
| .gitignore | ||
| Dockerfile | ||
| README.md | ||
| renovate.json | ||
| versions.env | ||
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:
- Builds the base image from
Dockerfileand pushes it as:latest. A local:blanktag is kept for the next step but not published. - Builds each
environments/<name>.Dockerfilewith--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_TOKENwrite 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_TOKENsecret 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:
-
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, leaveserver.connectionsempty 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.labelsalready covers theruns-on: ubuntuused by the publish workflow. The label image (catthehacker/ubuntu:act-latest) ships bash and the Docker CLI, which the job needs to rundocker build/docker push.
-
-
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), sodockerCLI steps inside jobs reach the same dind daemon. The hostname resolves inside dind via the--add-host=...:host-gatewayentry incontainer.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-storagevolume — 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):
- Node.js >= 20 on the host, then
npm install -g renovate@44. - 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/.envasRENOVATE_TOKEN(copy from.env.example). - Test without creating PRs:
./auto-update/run.sh --dry-run=full. - 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
*_VERSIONARG 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-slimand theruby:*-slim-trixieimage tag via the built-in dockerfile manager (the ruby tag updates theRUBY_VERSIONARG).- 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>.Dockerfilestarting withARG BASE_IMAGE/FROM ${BASE_IMAGE}, then add<name>to the loop in.forgejo/workflows/publish.yamland to the tag table above. - Tool versions are pinned as build arguments, so bumping is one line:
Dockerfile:NVM_VERSION,NODE_VERSIONenvironments/c-cpp.Dockerfile:CMAKE_VERSIONenvironments/go.Dockerfile:GO_VERSIONenvironments/zig.Dockerfile:ZIG_VERSIONenvironments/gradle.Dockerfile:GRADLE_VERSIONenvironments/maven.Dockerfile:MAVEN_VERSIONenvironments/ruby.Dockerfile:RUBY_VERSION(officialruby:*-slim-trixieimage tag)
Notes
- Rust and rust-nightly install toolchains into
/usr/local/rustupand/usr/local/cargo, shared across users of the image viaPATH. - The Go image does not include a C compiler; set
CGO_ENABLED=0or use thec-cppimage 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.