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
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:
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.
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.