- Go Template 73.5%
- Dockerfile 26.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| debian-ci | ||
| docs/superpowers | ||
| runner | ||
| README.md | ||
CI Images
One Debian-based CI runner image for cross-company use: debian-ci, a single
Containerfile with every toolchain pre-installed.
The image
git.kristofferopsahl.com/kristofferopsahl/debian-ci, built FROM debian:trixie-slim.
Everything is installed from Debian trixie's apt — no upstream tarballs, no third-party repos, no version managers. Versions are whatever trixie ships:
- Common: ca-certificates, curl, wget, git, git-lfs, openssh-client, rsync, sudo, gnupg, locales, file, procps, xz-utils, zstd, unzip, zip, jq.
- C / C++: build-essential (gcc/g++/make), clang, cmake, ninja-build, pkg-config, autoconf, automake, libtool, libssl-dev.
- Node: Node 20 + npm (also what JS-based actions like
actions/checkoutrun on). - Go: Go 1.24.
- Elixir: Elixir 1.18 + Erlang/OTP 27 + inotify-tools.
- Python: Python 3.13 + venv + pip + headers.
pythonsymlinked topython3. - Java: Java 21 (
default-jdk-headless) + Maven. - Rust: Rust + Cargo (~1.85) + rustfmt + clippy.
- PHP: PHP 8.4 + Composer + common extensions (mbstring, xml, curl, mysql, pgsql, zip, intl, bcmath, gd, sqlite3).
- Database clients: postgresql-client, default-mysql-client (MariaDB), sqlite3.
Runner labels
runs-on: |
Image |
|---|---|
debian-latest |
debian-ci:latest |
debian-13 |
debian-ci:latest — pins the Debian release; use it if a workflow must not move to Debian 14 when debian-latest does |
debian-trixie, debian-trixie-slim |
upstream docker.io/library/debian:trixie[-slim] passthroughs (configured in the runner, not built here). Unused day to day; kept as the recovery path for rebuilding a broken debian-ci |
Usage in downstream workflows
jobs:
test:
runs-on: debian-latest
steps:
- uses: actions/checkout@v4
- run: mix test
Adding a tool
Add the apt package to debian-ci/Containerfile and a version check to the
smoke-test RUN at the bottom. Commit and push to main. No runner change is
needed — the labels already point at debian-ci:latest.
If a tool isn't in trixie's apt, it doesn't go in the image; install it in the workflow that needs it.
Provisioning a runner host
See runner/README.md — pure cloud-init bootstrap from a
fresh Debian 13 cloud image, no snapshot needed.
Versioning
Versions follow Debian trixie's apt cadence. Weekly cron rebuild picks up Debian security updates.
Each build pushes two tags: :latest, which the runner labels use, and a dated
:YYYY-MM-DD. The final RUN in the Containerfile is a smoke test, so a build
with a broken toolchain fails before anything is pushed. If a pushed :latest
still misbehaves, roll back by pointing the labels in the runner's
config.yaml at the last good dated tag and restarting the runner.
The build prunes after pushing: it keeps the two newest dated tags (the current
build plus one rollback target) and deletes older ones, because the Forgejo box
is small and every rebuild is ~1 GB of new layers. REGISTRY_TOKEN therefore
needs write:package.
Reproducibility for project code is handled by actions/cache@v4 keyed on the
lockfile in downstream workflows.
Image build cycle
- On push to main when
debian-ci/Containerfileor the build workflow changes. - On pull request touching the same files — build and smoke test only, no push.
- Weekly Monday 04:00 UTC.
- Manual via Forgejo workflow dispatch.
The build job runs on debian-latest — the image builds itself. The smoke
test gates the push; if a pushed image still can't run the build, point the
runner's debian-latest label at the last good dated tag, or temporarily set
the job's runs-on: to debian-trixie-slim, and rebuild.