docs/ci.md and the workflow comment both described secrets.GITHUB_TOKEN as the working credential with REGISTRY_TOKEN as a fallback. That is backwards: the task token is rejected by Gitea's container registry (go-gitea/gitea#23642) and the first publish attempt failed on exactly that. REGISTRY_TOKEN is required. Part of #187 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
3.5 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 67MB 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:
bashandnodejs— act_runner runs JavaScript actions (actions/checkout,actions/cache,actions/upload-artifact) inside the job container and shellsrun:steps through bash. Without these, the first step of every job fails.coreutils,gawk,grep,sed,tar— GNU versions, because the workflow scripts usetacandgrep --include, which busybox does not provide. GNUtarmatters most:actions/cacheshells out totar --posix -P, and busybox rejects those flags, so every cache step fails without it.zstdis whatactions/cachereaches for over gzip when it is installed.curl,jq,git,zip,unzip— used byrelease.ymlandbin/build-zip.sh.intlandzipPHP extensions, plus Composer 2.mbstringis 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
- Add the version to the
phpmatrix in.gitea/workflows/ci-images.yml. - Merge to
main, or dispatch the workflow, and wait for the tag to appear. - Add the version to the
testmatrix 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.
Registry authentication
The build pushes with the REGISTRY_TOKEN secret, set at the organisation
level. This is required, not optional. Gitea's Actions task token
(secrets.GITHUB_TOKEN) is rejected by the container registry —
docker login fails with Get "https://git.unsupervised.ca/v2/": unauthorized. That is go-gitea/gitea#23642, open since 2023.
REGISTRY_TOKEN is a personal access token with the package scope, Read
and Write. The workflow logs in as github.actor, which must be the account
that owns the token; if it ever needs to differ, set a REGISTRY_USER
variable and the workflow will prefer it.