Files
register-referentie/docs/runbooks/gitea-actions-gotchas.md
Niek Otten 5f3dd31925
All checks were successful
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 46s
CI / unit (pull_request) Successful in 43s
CI / mutation (pull_request) Successful in 1m49s
CI / compose-smoke (pull_request) Successful in 4m2s
fix(ci): pin upload-artifact to @v3 — @v4 refuses to run on Gitea (refs #47)
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>
2026-06-25 15:28:07 +02:00

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 up or make 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, so docker compose -f infra/docker-compose.local.yml up -d just works.

Why not the obvious alternatives

  • Bake config into an image (incl. an inline Dockerfile) — docker compose up would then work unaided, but it's a build; we wanted the upstream images as-is.
  • Compose configs: with inline content — 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 --wait treats a one-shot exiting 0 as a failure unless something depends_on it with service_completed_successfully. flowable-init deploys the BPMN and exits with no dependant, so --wait fails the moment it does — last line container infra-flowable-init-1 exited (0).
  • The containerized runner can't reach published host ports, so an external curl localhost:8080/health can't work either.

Fixinfra/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

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