Files
ci-php/README.md
T
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

97 lines
3.6 KiB
Markdown

# 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
```yaml
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](https://github.com/go-gitea/gitea/issues/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.