thatguygriffandClaude Opus 5 2016cfbe20
Publish / PHP 8.1 (push) Successful in 6s
Publish / PHP 8.3 (push) Successful in 5s
Publish / PHP 8.5 (push) Successful in 6s
Publish / PHP 8.2 (push) Successful in 56s
Publish / PHP 8.4 (push) Successful in 1m17s
Shared PHP CI images for Gitea Actions
One image per PHP version, published to
git.unsupervised.ca/unsupervised/ci-php:<php-version>, so PHP projects can
run their jobs with `container:` instead of installing PHP per job.

Built on php:<version>-cli-alpine with Composer 2, the intl and zip
extensions, and the GNU CLI tools workflow scripts expect. bash and nodejs
are present because act_runner runs JavaScript actions inside the job
container; GNU tar because actions/cache shells out to `tar --posix -P`,
which busybox rejects.

Covers 8.1 through 8.5. Pushing needs the REGISTRY_TOKEN organisation secret
— Gitea's Actions task token cannot write packages (go-gitea/gitea#23642).

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

ci-php

Shared PHP CI images for Gitea Actions, one tag per PHP version.

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.4
git.unsupervised.ca/unsupervised/ci-php:8.5

The Unsupervised org is public, so these pull anonymously — consuming jobs need no registry credentials.

Using them

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: git.unsupervised.ca/unsupervised/ci-php:${{ matrix.php }}
    strategy:
      matrix:
        php: ['8.1', '8.2', '8.3', '8.4', '8.5']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: /composer/cache
          key: ${{ matrix.php }}-composer-${{ hashFiles('composer.lock') }}
      - run: composer install --prefer-dist --no-progress --no-interaction
      - run: composer test

COMPOSER_HOME is /composer, so /composer/cache is the path to cache.

Note that jobs.<id>.container.image cannot read the env context, so the registry path has to be written out at each use or come from a repo variable.

What is in them

Built on php:<version>-cli-alpine:

  • bash, nodejs — required by the runner, not by your build. act_runner executes JavaScript actions (actions/checkout, actions/cache, actions/upload-artifact) inside the job container and shells run: steps through bash. A job in an image without these fails on its first step.
  • coreutils, gawk, grep, sed, tar — GNU rather than busybox. tar is the one that matters: actions/cache shells out to tar --posix -P, which busybox rejects outright, so every cache step fails without it. zstd is what actions/cache prefers over gzip when present.
  • curl, git, jq, zip, unzip — what release and packaging scripts usually reach for.
  • PHP extensions: intl and zip on top of the official image, which already compiles in mbstring. Enough for phpunit, phpstan, phpcs and Composer.
  • Composer 2.

Roughly 67MB compressed.

If a project needs an extension that is not here, add it to the Dockerfile rather than installing it at job time — that is the whole point of the image.

Publishing

.gitea/workflows/publish.yml builds and pushes. It runs when Dockerfile changes on main, weekly so PHP patch releases and Alpine security updates land unattended, and on workflow_dispatch. Pull requests build every version without pushing.

Registry authentication

Pushing requires the REGISTRY_TOKEN secret, set at the organisation level. This is not optional: Gitea's Actions task token (secrets.GITHUB_TOKEN) is rejected by the container registry, failing with Get "https://git.unsupervised.ca/v2/": unauthorized. See go-gitea/gitea#23642.

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; set a REGISTRY_USER variable if it needs to differ.

Architecture

Images are built natively, so each carries the architecture of the runner that built it. Every runner in the pool is arm64. If a runner of another architecture joins, it will overwrite these tags with its own arch and the rest will fail to pull — that is the point to switch to docker buildx and a multi-arch manifest.

Adding a PHP version

Add it to the php matrix in .gitea/workflows/publish.yml and merge. Consuming projects can only reference a tag once it has been published.

S
Description
No description provided
Readme
30 KiB
Languages
Dockerfile 100%