Drops the inline-build images for the upstream services. The compose now references the published images directly (openzaak/open-zaak, openzaak/open-notificaties, keycloak, curl, flowable-rest) with no build for them, and the config they need is streamed into external named volumes by infra/seed-config.sh: rr-oz-config -> oz-init /app/setup_configuration (data.yaml) rr-kc-realms -> keycloak /opt/keycloak/data/import (realm exports) rr-fl-bpmn -> flowable-init /work (registratie.bpmn) How: the seeder creates each volume, `docker create`s a throwaway helper that mounts it, `docker cp`s the files in, and removes it. docker cp streams over the Docker API, so it works in Docker-in-Docker (the CI runner) where bind mounts mount empty. It uses plain `docker create`/`cp` — NOT `docker compose create`, which podman-compose (local dev) lacks. `external: true` fixed names keep the volumes identical across docker compose and podman-compose. Consequence: bare `docker compose up` no longer self-seeds, so use `make up` (seeds then starts). Every `*-up` target seeds first; `*-down` removes the external volume. acl/bff are still built (they're our apps, not upstream images). Verified end-to-end on podman-compose: `make keycloak-up` seeds rr-kc-realms, the upstream Keycloak mounts it, and --import-realm imports all four realms (digid realm returns 200). Seeder runs in ~2s. Docs updated: gitea-actions-gotchas.md, ci.md, openzaak.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.0 KiB
Gitea Actions gotchas
Known differences between Gitea Actions (our CI) and a plain local run, and the
workarounds we adopted. Referenced by CLAUDE.md §8.7 and §15.
Bind mounts don't reach Compose services on the hosted runner
Symptom. make smoke is green locally but the compose-smoke CI job fails
with the OpenZaak init container exiting 1:
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
class of failure hits any service that bind-mounts a workspace path —
nrc-init (its data.yaml), flowable-init (the BPMN), keycloak (the realm
import dir).
Cause. The ubuntu-latest runner executes the whole job inside a
container (docker.gitea.com/runner-images:ubuntu-latest). When the job then
runs docker compose ... up, Compose talks to the host's Docker daemon and
starts the stack as sibling containers. A relative bind mount such as
volumes:
- ./openzaak/setup_configuration:/app/setup_configuration:ro
is resolved by Compose to an absolute path inside the job container
(/workspace/eho/register-referentie/infra/openzaak/setup_configuration). The
daemon then looks for that path on its own host, doesn't find it, and
auto-creates an empty directory to mount. The container starts with an empty
mount point, so the file appears "missing".
This is the classic Docker-in-Docker / sibling-container bind-mount trap. It does
not happen on a runner that executes jobs directly on the host (the previous
self-hosted respellion-linux setup), which is why switching to ubuntu-latest
exposed it.
Fix: the upstream images are used verbatim (no build); config is streamed into
external named volumes with docker cp. infra/seed-config.sh creates a fixed-
name volume per asset, runs a throwaway helper container that mounts it, and
docker cps the files in. docker cp streams bytes over the Docker API, so it
works no matter where the daemon runs (including Docker-in-Docker). The services
then mount those volumes:
| Asset | External volume | Mounted by → at |
|---|---|---|
OpenZaak setup_configuration/data.yaml |
rr-oz-config |
oz-init → /app/setup_configuration |
| Keycloak realm exports | rr-kc-realms |
keycloak → /opt/keycloak/data/import |
workflows/registratie.bpmn |
rr-fl-bpmn |
flowable-init → /work |
The volumes are declared external: true with fixed name:s so they resolve
identically under docker compose and podman-compose. The seed step (make runs
it before every up) recreates them fresh each time; make down / the
per-service *-down targets remove them. Open Notificaties needs no config at all
— nrc-init runs migrations only.
Two hard constraints drove this design:
- Use plain
docker volume create/docker run/docker cp— notdocker compose create, which podman-compose (the local dev runtime) does not implement. docker cprather than a bind mount of the source dir, because that bind mount is exactly what fails on the containerized runner.
Consequence: bare docker compose up no longer self-seeds — the external
volumes must be populated first, so use make up (or make <svc>-up), which
seeds then starts. CI uses make smoke, which does the same.
Why not the alternatives.
- Bake into a (possibly inline) image — clean and portable, but it's a build; rejected here because the goal was to use the upstream images verbatim.
- Compose
configs:with inlinecontent— Compose materialises these as a temp file on the client side and bind-mounts it → same daemon-can't-see-it problem. - A self-hosted runner that runs jobs on the host — bind mounts would then work
with zero seeding, but it reintroduces a bespoke runner and undoes the move to
the hosted
ubuntu-latestlabel.
No bind mounts of these config files remain, so the SELinux :z/:Z relabel flag
is no longer needed anywhere in infra/ (named volumes don't need relabeling).
--wait fails on one-shot containers with no dependant
docker compose up --wait treats a service that exits as a failure of the
"stay up" condition — unless another service depends on it with
condition: service_completed_successfully. Our init jobs oz-init and
nrc-init are fine (openzaak/nrc-web depend on their completion), but
flowable-init deploys the BPMN and exits 0 with no dependant, so a
whole-project --wait fails the moment it exits — even with everything else
healthy. The symptom is a compose-smoke failure whose last compose line is:
container infra-flowable-init-1 exited (0)
Fix. The smoke does not --wait on the whole project. It starts
everything with up -d, then up -d --wait <services> only for the durable,
health-checked services (openzaak nrc-web acl bff — see WAIT_SVCS in the
Makefile). One-shots still run (and deploy), they just don't gate --wait.
This also removed the old external curl http://localhost:8080/health check:
the CI job runs in a container and can't reach published host ports at
localhost, and the per-service healthchecks (which run inside the
containers) already prove readiness, so --wait succeeding is the smoke.
--wait needs an explicit timeout
docker compose up --wait defaults to a 60-second timeout in some Compose v2
releases. A cold OpenZaak migrate alone takes ~50 s, so the smoke target passes
--wait-timeout 300 (see Makefile). The 3-minute Definition-of-Done budget
still holds — this just stops --wait giving up before the stack is healthy.
PostGIS readiness vs. pg_isready
pg_isready reports the server is accepting connections as soon as the TCP port
is open — before the postgis/postgis image has finished running its
CREATE EXTENSION postgis init scripts. An init container that starts migrating
in that window can fail on a missing PostGIS. The db healthchecks therefore add a
SELECT PostGIS_Version() probe so dependents wait for the extension, not just
the port.