Establishes the Gitea Actions CI pipeline for slice S-00-c and the full development stack that its compose-smoke job exercises.
CI workflow — .gitea/workflows/ci.yaml
Four jobs — lint, build, unit, compose-smoke — on the hosted ubuntu-latest runner (switched from the self-hosted respellion-linux label). Each job invokes the matching make target, so make ci locally runs exactly what CI runs (no drift). compose-smoke dumps 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.yml
Consolidates 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 Notificaties 1.16.1, Keycloak 26.1, PostGIS 17-3.5); only ACL and BFF are built from this repo.
Config delivery without bind mounts (the core problem this slice solved)
The ubuntu-latest runner executes the job inside a container, so docker compose starts 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 why oz-init kept failing with data.yaml does not exist. Fix:
infra/seed-config.sh streams config into external named volumes with docker 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 plain docker create/cp (not docker compose create, which podman-compose lacks), so the same mechanism works on the CI runner and on a local podman-compose box.
Open Notificaties needs no config — nrc-init runs migrations only (the OZ↔NRC notification wiring is deferred to S-06), which also avoids NRC's setup_configuration aborting with "No steps enabled" on an empty config.
Portable readiness — infra/wait-healthy.sh
Polls each durable service's healthcheck via docker ps + docker inspect '{{.State.Health.Status}}'. This replaces docker compose up --wait (not implemented by podman-compose) and an external curl localhost (the containerized runner can't reach published host ports), and it sidesteps --wait failing when a consumer-less one-shot (flowable-init) exits 0.
Hardening
PostGIS-aware db healthcheck — waits for SELECT PostGIS_Version(), not just an open TCP port, so init containers don't race the extension setup.
Pinned image tags (no :latest drift); on-failure log capture in CI.
Per-service stacks
infra/{openzaak,opennotificaties,keycloak,flowable}/docker-compose.yml were updated to the same mechanism (pure upstream images, external config volumes, migrations-only nrc-init, PostGIS healthcheck) so local make <svc>-up / -smoke stay 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 and compose-smoke fails immediately.
Docs
docs/runbooks/gitea-actions-gotchas.md (new) — the DinD bind-mount trap, the docker cp-into-volumes fix, the portable health poll, and PostGIS readiness.
docs/runbooks/ci.md — rewritten for the ubuntu-latest runner and the pipeline.
docs/runbooks/openzaak.md — config-via-volume and pinned tag.
Verification
CI (ubuntu-latest), run 32 / commit dda4c58:
Job
Result
lint
✅
build
✅
unit
✅
compose-smoke
✅
Local (rootless podman-compose):
Check
Result
make smoke
✅ full stack up, openzaak / nrc-web / acl / bff all healthy, clean teardown
Notes & follow-ups
Bare docker compose up no longer self-seeds the external config volumes — use make up (seeds, then starts). Documented in the gotchas runbook.
OZ↔NRC notification wiring (setup_configuration steps for NRC) lands in S-06; nrc-init is migrations-only until then.
## Summary
Establishes the Gitea Actions CI pipeline for slice **S-00-c** and the full development stack that its `compose-smoke` job exercises.
### CI workflow — `.gitea/workflows/ci.yaml`
Four jobs — `lint`, `build`, `unit`, `compose-smoke` — on the hosted **`ubuntu-latest`** runner (switched from the self-hosted `respellion-linux` label). Each job invokes the matching `make` target, so `make ci` locally runs exactly what CI runs (no drift). `compose-smoke` dumps 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.yml`
Consolidates 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 Notificaties `1.16.1`, Keycloak `26.1`, PostGIS `17-3.5`); only ACL and BFF are built from this repo.
### Config delivery without bind mounts (the core problem this slice solved)
The `ubuntu-latest` runner executes the job **inside a container**, so `docker compose` starts 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 why `oz-init` kept failing with `data.yaml does not exist`. Fix:
- **`infra/seed-config.sh`** streams config into **external named volumes** with `docker 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 plain `docker create`/`cp` (not `docker compose create`, which **podman-compose** lacks), so the same mechanism works on the CI runner and on a local podman-compose box.
- **Open Notificaties needs no config** — `nrc-init` runs migrations only (the OZ↔NRC notification wiring is deferred to S-06), which also avoids NRC's `setup_configuration` aborting with "No steps enabled" on an empty config.
### Portable readiness — `infra/wait-healthy.sh`
Polls each durable service's healthcheck via `docker ps` + `docker inspect '{{.State.Health.Status}}'`. This replaces `docker compose up --wait` (not implemented by podman-compose) and an external `curl localhost` (the containerized runner can't reach published host ports), and it sidesteps `--wait` failing when a consumer-less one-shot (`flowable-init`) exits 0.
### Hardening
- PostGIS-aware db healthcheck — waits for `SELECT PostGIS_Version()`, not just an open TCP port, so init containers don't race the extension setup.
- Pinned image tags (no `:latest` drift); on-failure log capture in CI.
### Per-service stacks
`infra/{openzaak,opennotificaties,keycloak,flowable}/docker-compose.yml` were updated to the same mechanism (pure upstream images, external config volumes, migrations-only `nrc-init`, PostGIS healthcheck) so local `make <svc>-up` / `-smoke` stay 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 and `compose-smoke` fails immediately.
### Docs
- **`docs/runbooks/gitea-actions-gotchas.md`** (new) — the DinD bind-mount trap, the `docker cp`-into-volumes fix, the portable health poll, and PostGIS readiness.
- `docs/runbooks/ci.md` — rewritten for the `ubuntu-latest` runner and the pipeline.
- `docs/runbooks/openzaak.md` — config-via-volume and pinned tag.
## Verification
**CI (`ubuntu-latest`), run 32 / commit `dda4c58`:**
| Job | Result |
|---|---|
| `lint` | ✅ |
| `build` | ✅ |
| `unit` | ✅ |
| `compose-smoke` | ✅ |
**Local (rootless podman-compose):**
| Check | Result |
|---|---|
| `make smoke` | ✅ full stack up, `openzaak` / `nrc-web` / `acl` / `bff` all healthy, clean teardown |
## Notes & follow-ups
- Bare `docker compose up` no longer self-seeds the external config volumes — use **`make up`** (seeds, then starts). Documented in the gotchas runbook.
- OZ↔NRC notification wiring (`setup_configuration` steps for NRC) lands in **S-06**; `nrc-init` is migrations-only until then.
closes #30
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the ACL multi-stage Dockerfile and .dockerignore, and expands
infra/docker-compose.yml from the BFF-only stub to the full development
stack (OpenZaak, NRC, Keycloak, Flowable, ACL, BFF). Without these
files a fresh checkout cannot satisfy `make smoke`'s `docker compose
up --build --wait` step, so `make ci` could never go green.
`make lint && make build && make unit` verified green locally.
`make smoke` requires Docker Compose v2 (`--wait` flag); on this dev box
only podman-compose is available — smoke will be verified on the
respellion-linux CI runner once it is registered (see docs/runbooks/ci.md).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Self-hosted respellion-linux runner not required — Gitea's hosted
ubuntu-latest runner has Docker + Compose v2 out of the box, so
make smoke works without any manual registration step.
Updates docs/runbooks/ci.md to reflect the new runner label and
removes the act_runner self-hosted setup as the primary path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
not
requested review from eho 2026-06-24 07:33:48 +00:00
latest bumped to OpenZaak 1.29.0 (2026-06-18) and open-notificaties
updated (2026-06-22), breaking oz-init in compose-smoke. Pin all four
compose files to stable patch releases:
open-zaak: 1.28.2 (was :latest -> 1.29.0)
open-notificaties: 1.16.1 (was :latest)
Tags are still overridable via OPENZAAK_TAG / OPENNOTIFICATIES_TAG env vars.
Also adds two if: failure() steps to the compose-smoke CI job: one that
dumps the last 100 lines of oz-init / nrc-init / acl / bff logs, and one
that tears the stack down cleanly, so future failures are self-diagnosing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three root-cause fixes for the oz-init CI failure:
1. Smoke timeout: add --wait-timeout 300 to `docker compose up --wait`
so CI has 5 minutes instead of the 60-second default in older Compose
v2 releases (migrations alone take 50 s locally).
2. PostGIS race: the old healthcheck used pg_isready which only checks
TCP connectivity — it passes before the postgis/postgis init scripts
have run SELECT PostGIS_Version(). The new check adds a psql probe so
oz-init does not start until PostGIS is actually available.
3. Remove :z from volume mounts: the SELinux re-label flag is
Podman/Fedora-specific and a no-op (or unexpected) under Docker on
ubuntu-latest; plain :ro is correct for both runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Replaces the three standalone Dockerfiles (openzaak, opennotificaties,
keycloak) with `build.dockerfile_inline` recipes in the compose files, so the
config bake has no separate Dockerfile artifacts to maintain. Behaviour is
identical: each derived image still COPYies its config in.
- oz-init / keycloak / flowable-init: 2-line inline Dockerfiles.
- Open Notificaties needs no bake at all now — nrc-init runs migrations only,
so all NRC services use the plain base image (removes a whole derived image).
Why dockerfile_inline and not `docker cp` into named volumes: docker cp avoids
images entirely but needs `docker compose create`, which podman-compose (the
local dev runtime) does not implement — it would break `make openzaak-up` etc.
locally. dockerfile_inline works on both podman-compose and the CI runner
(verified both: oz-init + keycloak inline builds locally; flowable-init inline
has been green on CI since run 27).
Docs updated: gitea-actions-gotchas.md and openzaak.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
`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>
not
changed title from 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)2026-06-25 09:19:09 +00:00
Adds infra/docker-compose.local.yml: the same full stack as the canonical
infra/docker-compose.yml, but the three config inputs (OpenZaak data.yaml,
Keycloak realms, Flowable BPMN) are bind-mounted from the repo instead of
streamed into external volumes by seed-config.sh.
Bind mounts are valid here because a local daemon (Docker Desktop on Windows/
macOS, or rootless Podman on Linux) can see the working directory — the seed
dance only exists for the containerized CI runner, where it can't. So this file
runs with a plain `docker compose up`: no make, no seed step, no bash.
docker compose -f infra/docker-compose.local.yml up -d --build
docker compose -f infra/docker-compose.local.yml up -d --build --wait # Docker Desktop
Linux/macOS convenience wrappers `make local` / `make local-down` added too.
Verified on podman: Keycloak boots from this file and imports the bind-mounted
realms (digid realm returns 200). docs/runbooks/ci.md documents the Windows path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructure for scannability: a shared root-cause intro, a quick-reference
table (gotcha → fix → where), and consistent Symptom/Why/Fix sections with
tighter prose. Documents infra/docker-compose.local.yml as the no-make/Windows
path and drops the now-stale "no bind mounts remain" line (the local compose
uses them, which is fine locally).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not
merged commit 5a83216395 into main2026-06-25 12:34:44 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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)