The artifact step failed the mutation job: upload-artifact@v4 bundles
@actions/artifact v2, which hard-aborts on any non-github.com server ("not
supported on GHES"), even though Gitea 1.25 stores artifacts fine. @v3 uses the
older protocol Gitea speaks and has no GHES guard — a drop-in swap (same inputs).
Document it as gotcha §4 and correct the CI runbook note.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.0 KiB
Gitea Actions gotchas
How our CI (Gitea Actions on the hosted ubuntu-latest runner) differs from a
local run, and the workarounds in this repo. Referenced by CLAUDE.md §8.7/§15.
One root cause sits under most of this: the runner executes the job inside a
container, so when a step runs docker compose up, Compose starts the stack as
sibling containers on the host's daemon. Anything that assumes the job and
those containers share a filesystem — or a localhost — breaks.
| Gotcha | Fix | Lives in |
|---|---|---|
| Bind-mounted config arrives empty | docker cp config into external volumes |
infra/seed-config.sh |
docker compose up --wait is unsupported / flaky |
poll health with docker inspect |
infra/wait-healthy.sh |
pg_isready passes before PostGIS is ready |
add a PostGIS_Version() probe |
the db healthchecks |
upload-artifact@v4 fails ("not supported on GHES") |
pin @v3 |
.gitea/workflows/ci.yaml (mutation job) |
1. Bind mounts don't reach the containers
Symptom — green locally, but compose-smoke fails with:
oz-init-1 | CommandError: Yaml file `/app/setup_configuration/data.yaml` does not exist.
Migrations run fine; only the step that reads a mounted file fails. The same
trap hits nrc-init, flowable-init, and keycloak.
Why — a relative bind mount like ./openzaak/setup_configuration:/app/... is
resolved by Compose to a path inside the job container
(/workspace/.../setup_configuration). The daemon then looks for that path on
its own host, doesn't find it, and mounts an empty directory. (It works on a
runner that executes jobs on the host — which is why moving to ubuntu-latest
exposed it.)
Fix — use the upstream images verbatim (no build) and stream config into
external named volumes with docker cp, which copies over the Docker API and
so works wherever the daemon runs. infra/seed-config.sh creates each volume,
mounts it in a throwaway helper, and copies the files in:
| Asset | Volume | Mounted at |
|---|---|---|
OpenZaak data.yaml |
rr-oz-config |
oz-init:/app/setup_configuration |
| Keycloak realms | rr-kc-realms |
keycloak:/opt/keycloak/data/import |
registratie.bpmn |
rr-fl-bpmn |
flowable-init:/work |
The volumes are external: true with fixed names, so they resolve identically
under docker compose and podman-compose. make seeds before every up; make down removes them. (Open Notificaties needs nothing — nrc-init migrates only.)
Consequence — bare docker compose up can't self-seed external volumes:
- CI / Linux / macOS:
make upormake smoke(seed, then start). - No-make / Windows:
infra/docker-compose.local.yml— a twin stack that bind-mounts the config instead. Bind mounts are fine locally because a local daemon can see your working directory, sodocker compose -f infra/docker-compose.local.yml up -djust works.
Why not the obvious alternatives
- Bake config into an image (incl. an inline Dockerfile) —
docker compose upwould then work unaided, but it's a build; we wanted the upstream images as-is. - Compose
configs:with inlinecontent— Compose writes a client-side temp file and bind-mounts it, hitting the exact same problem. - A host-executing runner — bind mounts would work with zero seeding, but it
reintroduces a self-hosted runner and undoes the move to
ubuntu-latest.
2. Readiness: poll health, don't use --wait
docker compose up --wait looks ideal but fails us three ways:
- podman-compose doesn't implement it (
unrecognized arguments: --wait) — so it would break local dev. - A project-wide
--waittreats a one-shot exiting0as a failure unless somethingdepends_onit withservice_completed_successfully.flowable-initdeploys the BPMN and exits with no dependant, so--waitfails the moment it does — last linecontainer infra-flowable-init-1 exited (0). - The containerized runner can't reach published host ports, so an external
curl localhost:8080/healthcan't work either.
Fix — infra/wait-healthy.sh polls each durable service (openzaak nrc-web acl bff, listed as WAIT_SVCS in the Makefile) with docker ps + docker inspect '{{.State.Health.Status}}' until it reports healthy. It uses only
primitives both runtimes support, reads the in-container healthcheck (no host
port needed), and ignores the one-shots (they only need to have run).
WAIT_TIMEOUT defaults to 420 s — enough for the cold OpenZaak migrate (~90 s)
plus app start.
3. pg_isready passes before PostGIS is ready
pg_isready succeeds as soon as the TCP port is open — before the
postgis/postgis image has finished running CREATE EXTENSION postgis. An init
container that starts migrating in that window can fail on a missing PostGIS. So
the db healthchecks add a SELECT PostGIS_Version() probe, making dependents wait
for the extension, not just the port.
4. actions/upload-artifact@v4 refuses to run on Gitea
Symptom — the mutation job's make mutation step passes (95% score), but the
upload step right after it fails the job:
::error::@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+
are not currently supported on GHES.
❌ Failure - Main https://github.com/actions/upload-artifact@v4
Why — upload-artifact@v4 bundles @actions/artifact v2, which inspects the
server URL and hard-aborts on anything that isn't github.com, treating Gitea
as an unsupported GitHub Enterprise Server. The check fires regardless of whether
the Gitea server can actually store artifacts (1.24+ can). It is the action, not
the server, that refuses.
Fix — pin actions/upload-artifact@v3 (and download-artifact@v3 if ever
needed). v3 uses the older artifact protocol that Gitea implements, and has no GHES
guard. Inputs are the same (name, path, if-no-files-found), so it is a drop-in
swap. Do not bump to @v4 until act_runner advertises github.com-compatible
artifact support.