Files
unsupervised-scheduler/docs/ci.md
T
thatguygriffandClaude Opus 5 28f586d207
CI Images / Build CI image (PHP 8.1) (pull_request) Successful in 48s
CI / Tests (PHP 8.1) (pull_request) Successful in 55s
CI Images / Build CI image (PHP 8.2) (pull_request) Successful in 1m46s
CI Images / Build CI image (PHP 8.3) (pull_request) Successful in 1m48s
CI Images / Build CI image (PHP 8.5) (pull_request) Successful in 2m26s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m50s
CI / Tests (PHP 8.5) (pull_request) Successful in 2m58s
CI / Coding Standards & Static Analysis (pull_request) Failing after 17m18s
CI / Build Plugin Zip (pull_request) Skipped
Install GNU tar in the CI image so actions/cache works
actions/cache shells out to `tar --posix -P`. Alpine's busybox tar rejects
both flags, so the cache step would fail in every job that runs inside these
images — which is all of them once ci.yml switches over.

coreutils does not cover this: tar is its own Alpine package. Add it, add
zstd (which actions/cache prefers over gzip when present), and assert GNU
tar in the image's smoke test so a future base-image change cannot quietly
drop it again.

Part of #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:15:40 -03:00

3.1 KiB

CI images

CI and release jobs do not install PHP. They run inside prebuilt images published to the Gitea container registry:

git.unsupervised.ca/unsupervised/ci-php:8.1
git.unsupervised.ca/unsupervised/ci-php:8.2
git.unsupervised.ca/unsupervised/ci-php:8.3
git.unsupervised.ca/unsupervised/ci-php:8.5

The Unsupervised org is public, so the packages pull anonymously — jobs need no registry credentials to use them.

Why

shivammathur/setup-php installs PHP 8.3+ from apt/the ondrej PPA on these arm64 runners. That was a ~145s floor against ~35s for 8.1 and 8.2, with a tail that twice ran past the step timeout and failed the run outright (#178). Caching the .debs helped, but the apt step itself remained, and PHP 8.5 has the same shape of problem. Pulling a ~120MB image from a registry inside the cluster replaces the whole thing (#187).

What is in the image

.gitea/ci/Dockerfile builds on php:<version>-cli-alpine and adds:

  • bash and nodejs — act_runner runs JavaScript actions (actions/checkout, actions/cache, actions/upload-artifact) inside the job container and shells run: steps through bash. Without these, the first step of every job fails.
  • coreutils, gawk, grep, sed, tar — GNU versions, because the workflow scripts use tac and grep --include, which busybox does not provide. GNU tar matters most: actions/cache shells out to tar --posix -P, and busybox rejects those flags, so every cache step fails without it. zstd is what actions/cache reaches for over gzip when it is installed.
  • curl, jq, git, zip, unzip — used by release.yml and bin/build-zip.sh.
  • intl and zip PHP extensions, plus Composer 2. mbstring is already compiled into the official images.

Publishing

.gitea/workflows/ci-images.yml builds and pushes them. It runs when the Dockerfile changes on main, weekly (so PHP patch releases and Alpine security updates land on their own), and on workflow_dispatch. On a pull request it builds without pushing, so a broken Dockerfile is caught before it reaches main.

Adding or dropping a PHP version

  1. Add the version to the php matrix in .gitea/workflows/ci-images.yml.
  2. Merge to main, or dispatch the workflow, and wait for the tag to appear.
  3. Add the version to the test matrix in .gitea/workflows/ci.yml.

Steps 2 and 3 cannot be one commit: a job cannot run in an image that has not been published yet.

Architecture

The images are built natively on whichever runner picks the job, so they carry that runner's architecture only. Every runner in the pool is arm64 today. If one of a different architecture ever joins, it will overwrite these tags with its own arch and the rest will fail to pull — at which point the build needs docker buildx and a multi-arch manifest.

If a push is refused

The build authenticates with secrets.GITHUB_TOKEN, the Actions task token. If the registry ever refuses it, create a personal access token with package:write, store it as the REGISTRY_TOKEN secret, and optionally set the REGISTRY_USER variable — the workflow prefers both when present.