ci(infra): Gitea Actions CI pipeline + full-stack compose smoke (closes #30) #50
Reference in New Issue
Block a user
Delete Branch "ci/30-gitea-actions-ci"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Establishes the Gitea Actions CI pipeline for slice S-00-c and the full development stack that its
compose-smokejob exercises.CI workflow —
.gitea/workflows/ci.yamlFour jobs —
lint,build,unit,compose-smoke— on the hostedubuntu-latestrunner (switched from the self-hostedrespellion-linuxlabel). Each job invokes the matchingmaketarget, somake cilocally runs exactly what CI runs (no drift).compose-smokedumps container logs and tears the stack down on failure.uses:are absolute, tag-pinned URLs per CLAUDE.md §8.7/§15.Full development stack —
infra/docker-compose.ymlConsolidates OpenZaak, Open Notificaties (NRC), Keycloak, Flowable and our ACL + BFF into one stack. Upstream images are used verbatim and pinned (OpenZaak
1.28.2, Open Notificaties1.16.1, Keycloak26.1, PostGIS17-3.5); only ACL and BFF are built from this repo.Config delivery without bind mounts (the core problem this slice solved)
The
ubuntu-latestrunner executes the job inside a container, sodocker composestarts the stack as sibling containers via the host daemon. A workspace bind mount then resolves to a path the daemon can't see and is mounted empty — which is whyoz-initkept failing withdata.yaml does not exist. Fix:infra/seed-config.shstreams config into external named volumes withdocker cp(which works in Docker-in-Docker because it streams over the API):rr-oz-config←setup_configuration/data.yaml,rr-kc-realms← the four realm exports,rr-fl-bpmn←registratie.bpmn. It uses plaindocker create/cp(notdocker compose create, which podman-compose lacks), so the same mechanism works on the CI runner and on a local podman-compose box.nrc-initruns migrations only (the OZ↔NRC notification wiring is deferred to S-06), which also avoids NRC'ssetup_configurationaborting with "No steps enabled" on an empty config.Portable readiness —
infra/wait-healthy.shPolls each durable service's healthcheck via
docker ps+docker inspect '{{.State.Health.Status}}'. This replacesdocker compose up --wait(not implemented by podman-compose) and an externalcurl localhost(the containerized runner can't reach published host ports), and it sidesteps--waitfailing when a consumer-less one-shot (flowable-init) exits 0.Hardening
SELECT PostGIS_Version(), not just an open TCP port, so init containers don't race the extension setup.:latestdrift); on-failure log capture in CI.Per-service stacks
infra/{openzaak,opennotificaties,keycloak,flowable}/docker-compose.ymlwere updated to the same mechanism (pure upstream images, external config volumes, migrations-onlynrc-init, PostGIS healthcheck) so localmake <svc>-up/-smokestay consistent with the consolidated stack.New service build
services/acl/Dockerfile+.dockerignore— multi-stage .NET 10 build. Without these a fresh checkout cannot build the ACL image andcompose-smokefails immediately.Docs
docs/runbooks/gitea-actions-gotchas.md(new) — the DinD bind-mount trap, thedocker cp-into-volumes fix, the portable health poll, and PostGIS readiness.docs/runbooks/ci.md— rewritten for theubuntu-latestrunner and the pipeline.docs/runbooks/openzaak.md— config-via-volume and pinned tag.Verification
CI (
ubuntu-latest), run 32 / commitdda4c58:lintbuildunitcompose-smokeLocal (rootless podman-compose):
make smokeopenzaak/nrc-web/acl/bffall healthy, clean teardownNotes & follow-ups
docker compose upno longer self-seeds the external config volumes — usemake up(seeds, then starts). Documented in the gotchas runbook.setup_configurationsteps for NRC) lands in S-06;nrc-initis migrations-only until then.closes #30
🤖 Generated with Claude Code
Root cause of the compose-smoke failure (found in the runner logs): oz-init-1 | CommandError: Yaml file `/app/setup_configuration/data.yaml` does not exist. The ubuntu-latest runner runs the job inside a container, so `docker compose up` starts the stack as SIBLING containers via the host daemon. A relative bind mount (./openzaak/setup_configuration) resolves to a path inside the job container that the daemon can't see, so Docker mounts an empty dir and the init container can't find data.yaml. The same trap hit nrc-init (data.yaml), flowable-init (the BPMN) and keycloak (realm import). Fix: bake the assets into small derived images instead of bind-mounting: - infra/openzaak/Dockerfile -> register-referentie/openzaak:dev - infra/opennotificaties/Dockerfile-> register-referentie/opennotificaties:dev - infra/keycloak/Dockerfile -> register-referentie/keycloak:dev - flowable-init: build.dockerfile_inline bakes workflows/registratie.bpmn Base versions stay build args (OPENZAAK_TAG / OPENNOTIFICATIES_TAG), so the pinning is unchanged. Applied to both the consolidated compose and the per-service composes, so local Podman and CI use one mechanism — no bind mounts, no SELinux `:z`, no world-readable requirement. Verified locally: `podman build` of the OpenZaak and BPMN images produces the file at the expected in-container path. Docs: docs/runbooks/gitea-actions-gotchas.md explains the DinD bind-mount trap and the bake fix; openzaak.md and ci.md point at it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>With OpenZaak now coming up, nrc-init ran for the first time and failed: nrc-init-1 | CommandError: No steps enabled, aborting. NRC's setup_configuration/data.yaml is intentionally empty ({}) — the OZ<->NRC wiring is deferred to S-06 — but /setup_configuration.sh runs `manage.py setup_configuration` regardless, and NRC 1.16.1 aborts when no steps are enabled. (This was masked until now: oz-init failed first, so openzaak never became healthy and nrc-init, which waits on it, never ran.) The documented intent is "init runs migrations only", so nrc-init now runs `manage.py migrate` directly instead of /setup_configuration.sh, and the dead RUN_SETUP_CONFIG env is dropped from the NRC services. nrc-web still migrates + creates the superuser itself via /start.sh. Also: - Makefile: bump compose `--wait-timeout` 300 -> 420. The serial oz-db -> oz-init -> openzaak(healthy) -> nrc-init -> nrc-web(healthy) chain runs ~260 s on the runner; 420 s gives comfortable headroom. - ci.yaml: widen the on-failure log dump to oz-init, openzaak, nrc-init, nrc-web, flowable-init, keycloak, acl, bff for full diagnosability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Run 28 got the full stack healthy but `compose-smoke` still failed. The last compose line before the error was: container infra-flowable-init-1 exited (0) `docker compose up --wait` treats a service that exits as a failure of the "stay running" condition unless something depends on it via `service_completed_successfully`. oz-init/nrc-init are fine (openzaak/nrc-web depend on them), but flowable-init deploys the BPMN and exits 0 with no dependant, so whole-project `--wait` failed the instant it finished — even though everything else was healthy and nrc-init now exits 0. Smoke now: 1. `up -d` starts the full stack (one-shots run + deploy as before), then 2. `up -d --wait <WAIT_SVCS>` waits only for the durable health-checked services (openzaak nrc-web acl bff). Also drops the external `curl localhost:8080/health`: the containerized CI runner can't reach published host ports at localhost, and each service's healthcheck already runs inside its container — so `--wait` succeeding IS the smoke. Documented in docs/runbooks/gitea-actions-gotchas.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`make smoke` errored locally because podman-compose doesn't implement `docker compose up --wait` (`unrecognized arguments: --wait`). Replace the `--wait` step with infra/wait-healthy.sh, which polls each durable health-checked service ($(WAIT_SVCS)) via `docker ps` + `docker inspect '{{.State.Health.Status}}'`. This: - works on both docker compose (CI) and podman-compose (local) — only plain docker primitives, no `--wait`; - reads the in-container healthcheck, so it needs no host port access (the CI runner can't reach published ports); - ignores the one-shot init jobs, sidestepping the "--wait fails when a consumer-less one-shot exits 0" issue (flowable-init). Verified on podman-compose: wait-healthy.sh reports bff healthy (rc=0); podman exposes .State.Health.Status (starting -> healthy) and the name filter matches both `_` and `-` container naming. Docs: gitea-actions-gotchas.md updated (the two `--wait` sections folded into one "portable health poll" section). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>ci(infra): ACL Dockerfile + full compose stack for make smoke (closes #30)to ci(infra): Gitea Actions CI pipeline + full-stack compose smoke (closes #30)