No description
  • Dockerfile 83.1%
  • HCL 16.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-01 10:25:53 +00:00
.github/workflows Run the build on ubuntu-latest, not the image it builds 2026-07-22 11:09:55 +02:00
fs/root/.ssh Add Docker bake configuration and update Dockerfile for PHP version management 2026-06-30 19:49:41 +02:00
.gitignore Squashed commit of the following: 2025-11-04 03:58:30 +01:00
compose.yml Publish direct to GHCR and Docker Hub, drop git.grey.ooo mirroring 2026-07-22 10:20:01 +02:00
docker-bake.hcl Publish direct to GHCR and Docker Hub, drop git.grey.ooo mirroring 2026-07-22 10:20:01 +02:00
Dockerfile Add tea, gh and ripgrep to the runner image 2026-08-09 14:13:25 +02:00
README.md Add tea, gh and ripgrep to the runner image 2026-08-09 14:13:25 +02:00

Github-Actions-Runner (GHAR)

The house runner image for Forgejo Actions at git.grey.ooo. Jobs reference it through an explicit container: key rather than relying on whatever the runner host happens to have, so what is in this image is what CI can call.

jobs:
  build:
    runs-on: docker
    container:
      image: ghcr.io/matthewbaggett/act-runner:php8.4

Where to pull it from

Registry Reference
GHCR (prefer this) ghcr.io/matthewbaggett/act-runner:php<version>
Docker Hub matthewbaggett/act-runner:php<version>

Both registries get every tag on every publish. Pull from GHCR anyway: Docker Hub rate-limits anonymous pulls at roughly 100/6h per IP, and the Forgejo runners pull with force_pull on every job, which has exhausted that quota and broken every job at the setup step.

Tags are php7.4, php8.1, php8.2, php8.3, php8.4, php8.5, plus latest as an alias for the current default (PHP_VERSION_CURRENT in docker-bake.hcl, today 8.4).

main publishes linux/amd64 and linux/arm64; branch and PR builds are amd64-only to keep them quick.

What's in it

Alpine-based, on top of matthewbaggett/php:<version>.

Area Tools
PHP php (7.48.5 per tag) with xdebug, composer
Forge CLIs tea (Gitea/Forgejo), gh (GitHub — read the caveat below)
Containers docker, docker compose, docker buildx
Languages go, node, npm, yarn
Infra tofu (OpenTofu), aws, eksctl, aws-iam-authenticator
Data psql, mysql, sqlite3, redis-cli
Text/JSON jq, yq, rg (ripgrep), grep, sed, awk
General git, ssh, curl, wget, rsync, make, g++, openssl, gpg, nmap, xorriso, nginx

git.grey.ooo's host key is pre-seeded in /root/.ssh/known_hosts (port 222) so git-over-ssh works without an interactive prompt.

gh is present, and that is not a licence to use it

gh is installed because some repos built by this image genuinely live on github.com.

It is banned outright in Grey.ooo/Someones.Computer, and in any other repo whose GitHub side is a read-only mirror. For those, Forgejo is canonical: code, issues, PRs and CI all live at git.grey.ooo, mirrored CI does not run on GitHub, and gh will happily report stale or absent state without saying so. That failure is silent, which is why the rule there is "not even read-only". Use tea instead — it is in this image for exactly that reason.

If you are unsure which kind of repo you are in, check its CLAUDE.md/contributing docs before reaching for gh.

What is deliberately not in it

hadolint, markdownlint and actionlint are intentionally absent. Consumers run those as pinned docker images (see bin/lint-*.sh in Someones.Computer) so that CI and a laptop execute byte-identical versions. Installing them here would silently break that guarantee by giving CI a different build from the one a developer runs. The same reasoning covers dive and the Playwright image.

Adding a linter to this image is therefore a regression, not a convenience — don't.

Adding a tool

Size is a real constraint: the runners force_pull this image on every job on a shared, serialised x86_64 worker, so every megabyte is paid repeatedly.

  1. Prefer a single static binary from the upstream release over a package-manager install that drags in a dependency tree. Alpine is musl — check the binary is musl-compatible or statically linked (Go and Rust-musl builds are fine; glibc-only builds are not, which is why session-manager-plugin is absent).
  2. Pin the version as an ARG next to the RUN block that consumes it, so a bump is a one-line diff rather than silent drift. Current pins: TOFU_VERSION, EKSCTL_VERSION, AWS_IAM_AUTHENTICATOR_VERSION, TEA_VERSION, GH_VERSION, RIPGREP_VERSION.
  3. Verify the checksum against the upstream checksums file. Every existing block does.
  4. Clean up the download. Fetch into a mktemp -d with trap 'rm -rf "$WORK"' EXIT and extract only the binary you need. Archives left in /tmp are shipped and re-pulled forever — this cost the image ~100 MiB before it was noticed.
  5. Cover both arches. main builds amd64 and arm64; a block that only handles one will fail the multi-arch publish even though the PR build passed.
  6. Run the tool once inside the block (tea --version) so a bad download fails the build rather than the job that needs it.

Building locally

docker buildx bake                       # every PHP variant, amd64
docker build --target runner \
  --build-arg PHP_VERSION=8.5 -t ghar .  # one variant, quick
docker run --rm -i hadolint/hadolint hadolint - < Dockerfile

Publishing happens from .github/workflows/build.yml on pushes to main only. The workflow deliberately does not run inside this image — that would make the build depend on its own output and break the moment a tag is missing or unpullable.