refactor(infra): use upstream images verbatim, seed config via docker cp (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 49s
CI / build (pull_request) Successful in 44s
CI / unit (pull_request) Successful in 44s
CI / compose-smoke (pull_request) Successful in 4m15s

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>
This commit is contained in:
2026-06-25 10:22:14 +02:00
parent 6d8e1d0830
commit b349dff496
9 changed files with 182 additions and 100 deletions

View File

@@ -12,6 +12,14 @@ COMPOSE := infra/docker-compose.yml
# a one-shot with no `service_completed_successfully` dependant exits (flowable-init), # a one-shot with no `service_completed_successfully` dependant exits (flowable-init),
# so we wait on the durable services instead. See docs/runbooks/gitea-actions-gotchas.md. # so we wait on the durable services instead. See docs/runbooks/gitea-actions-gotchas.md.
WAIT_SVCS := openzaak nrc-web acl bff WAIT_SVCS := openzaak nrc-web acl bff
# Config files (OpenZaak data.yaml, Keycloak realms, Flowable BPMN) are streamed
# into external named volumes via `docker cp` (infra/seed-config.sh) instead of
# bind-mounted, because bind mounts don't reach sibling containers on the
# containerized CI runner. SEED populates them; run it before every `up`. The
# volumes are `external`, so compose won't remove them — CFG_VOLS lists them for
# explicit teardown. See docs/runbooks/gitea-actions-gotchas.md.
SEED := bash infra/seed-config.sh
CFG_VOLS := rr-oz-config rr-kc-realms rr-fl-bpmn
OZ_COMPOSE := infra/openzaak/docker-compose.yml OZ_COMPOSE := infra/openzaak/docker-compose.yml
OZ_BASE := http://localhost:8000 OZ_BASE := http://localhost:8000
NRC_COMPOSE := infra/opennotificaties/docker-compose.yml NRC_COMPOSE := infra/opennotificaties/docker-compose.yml
@@ -32,7 +40,7 @@ export DOCKER_HOST := unix://$(PODMAN_SOCK)
endif endif
endif endif
.PHONY: ci lint build unit smoke down changelog openzaak-up openzaak-smoke openzaak-seed openzaak-down stack-up stack-smoke stack-down keycloak-up keycloak-smoke keycloak-down flowable-up flowable-smoke flowable-down help .PHONY: ci lint build unit smoke up down changelog openzaak-up openzaak-smoke openzaak-seed openzaak-down stack-up stack-smoke stack-down keycloak-up keycloak-smoke keycloak-down flowable-up flowable-smoke flowable-down help
## ci: run the full pipeline — lint, build, unit, smoke (mirrors Gitea Actions) ## ci: run the full pipeline — lint, build, unit, smoke (mirrors Gitea Actions)
ci: lint build unit smoke ci: lint build unit smoke
@@ -49,19 +57,28 @@ build:
unit: unit:
dotnet test $(SLN) -c Release dotnet test $(SLN) -c Release
## smoke: bring the whole stack up, wait for the health-checked services, tear down ## smoke: seed config, bring the whole stack up, wait for health-checked services, tear down
# Step 1 starts EVERYTHING (incl. one-shot init jobs that deploy and exit 0). # SEED populates the external config volumes first (the upstream images are used
# Step 2 waits only for the durable, health-checked services ($(WAIT_SVCS)) — see # verbatim — no build for them). `up -d --build` then starts EVERYTHING (building
# WAIT_SVCS above for why the one-shots are excluded. The healthchecks run inside # only our acl/bff). The second `up --wait` waits for the durable, health-checked
# the containers, so this needs no host port access (the CI runner can't reach # services ($(WAIT_SVCS)); the one-shots are excluded because `--wait` fails when a
# published ports anyway). # one-shot with no `service_completed_successfully` dependant exits (flowable-init).
# Healthchecks run inside the containers, so no host port access is needed.
smoke: smoke:
$(SEED) oz kc fl
docker compose -f $(COMPOSE) up -d --build docker compose -f $(COMPOSE) up -d --build
bash -c 'docker compose -f $(COMPOSE) up -d --wait --wait-timeout 420 $(WAIT_SVCS); rc=$$?; docker compose -f $(COMPOSE) down --volumes; exit $$rc' bash -c 'docker compose -f $(COMPOSE) up -d --wait --wait-timeout 420 $(WAIT_SVCS); rc=$$?; docker compose -f $(COMPOSE) down --volumes; docker volume rm -f $(CFG_VOLS) >/dev/null 2>&1; exit $$rc'
## down: stop and remove the local stack ## up: seed config volumes and start the full stack (use instead of bare
## `docker compose up`, which can't self-seed the external config volumes)
up:
$(SEED) oz kc fl
docker compose -f $(COMPOSE) up -d --build
## down: stop and remove the local stack (incl. the external config volumes)
down: down:
docker compose -f $(COMPOSE) down --volumes docker compose -f $(COMPOSE) down --volumes
-docker volume rm -f $(CFG_VOLS)
## changelog: regenerate CHANGELOG.md from Conventional Commits (git-cliff) ## changelog: regenerate CHANGELOG.md from Conventional Commits (git-cliff)
changelog: changelog:
@@ -69,11 +86,11 @@ changelog:
## openzaak-up: start the OpenZaak stack (migrations run on first start) ## openzaak-up: start the OpenZaak stack (migrations run on first start)
openzaak-up: openzaak-up:
$(SEED) oz
docker compose -f $(OZ_COMPOSE) up -d docker compose -f $(OZ_COMPOSE) up -d
## openzaak-smoke: start OpenZaak, then assert it is up with auth enforced ## openzaak-smoke: start OpenZaak, then assert it is up with auth enforced
openzaak-smoke: openzaak-smoke: openzaak-up
docker compose -f $(OZ_COMPOSE) up -d
@bash -c 'set -e; \ @bash -c 'set -e; \
echo "waiting for OpenZaak to respond..."; \ echo "waiting for OpenZaak to respond..."; \
for i in $$(seq 1 60); do \ for i in $$(seq 1 60); do \
@@ -97,9 +114,11 @@ openzaak-seed: openzaak-up
## openzaak-down: stop and remove the OpenZaak stack (wipes data) ## openzaak-down: stop and remove the OpenZaak stack (wipes data)
openzaak-down: openzaak-down:
docker compose -f $(OZ_COMPOSE) down --volumes docker compose -f $(OZ_COMPOSE) down --volumes
-docker volume rm -f rr-oz-config
## stack-up: start OpenZaak + Open Notificaties together (shared network) ## stack-up: start OpenZaak + Open Notificaties together (shared network)
stack-up: stack-up:
$(SEED) oz
docker compose $(STACK_FILES) up -d docker compose $(STACK_FILES) up -d
## stack-smoke: start both, assert OpenZaak (403/302/200) and NRC (302) are reachable ## stack-smoke: start both, assert OpenZaak (403/302/200) and NRC (302) are reachable
@@ -119,9 +138,11 @@ stack-smoke: stack-up
## stack-down: stop and remove both stacks (wipes data) ## stack-down: stop and remove both stacks (wipes data)
stack-down: stack-down:
docker compose $(STACK_FILES) down --volumes docker compose $(STACK_FILES) down --volumes
-docker volume rm -f rr-oz-config
## keycloak-up: start Keycloak with the four imported realms ## keycloak-up: start Keycloak with the four imported realms
keycloak-up: keycloak-up:
$(SEED) kc
docker compose -f $(KC_COMPOSE) up -d docker compose -f $(KC_COMPOSE) up -d
## keycloak-smoke: start Keycloak, then verify each realm logs in + returns its claim ## keycloak-smoke: start Keycloak, then verify each realm logs in + returns its claim
@@ -134,9 +155,11 @@ keycloak-smoke: keycloak-up
## keycloak-down: stop and remove Keycloak ## keycloak-down: stop and remove Keycloak
keycloak-down: keycloak-down:
docker compose -f $(KC_COMPOSE) down --volumes docker compose -f $(KC_COMPOSE) down --volumes
-docker volume rm -f rr-kc-realms
## flowable-up: start Flowable (deploys registratie.bpmn on boot) ## flowable-up: start Flowable (deploys registratie.bpmn on boot)
flowable-up: flowable-up:
$(SEED) fl
docker compose -f $(FL_COMPOSE) up -d docker compose -f $(FL_COMPOSE) up -d
## flowable-smoke: start Flowable, then verify a started instance waits on the external task ## flowable-smoke: start Flowable, then verify a started instance waits on the external task
@@ -149,6 +172,7 @@ flowable-smoke: flowable-up
## flowable-down: stop and remove Flowable ## flowable-down: stop and remove Flowable
flowable-down: flowable-down:
docker compose -f $(FL_COMPOSE) down --volumes docker compose -f $(FL_COMPOSE) down --volumes
-docker volume rm -f rr-fl-bpmn
## help: list available targets ## help: list available targets
help: help:

View File

@@ -16,16 +16,18 @@ and CI cannot drift:
| `lint` | `make lint``dotnet format … --verify-no-changes` | .NET 10 SDK | | `lint` | `make lint``dotnet format … --verify-no-changes` | .NET 10 SDK |
| `build` | `make build``dotnet build … -c Release` | .NET 10 SDK | | `build` | `make build``dotnet build … -c Release` | .NET 10 SDK |
| `unit` | `make unit``dotnet test … -c Release` | .NET 10 SDK | | `unit` | `make unit``dotnet test … -c Release` | .NET 10 SDK |
| `compose-smoke` | `make smoke``up -d` (full stack) → `up --wait` durable services → `down` | container engine + compose v2 | | `compose-smoke` | `make smoke` seed config volumes → `up -d` (full stack) → `up --wait` durable services → `down` | container engine + compose v2 |
All `uses:` references are absolute, tag-pinned URLs (`https://github.com/actions/checkout@v4`, All `uses:` references are absolute, tag-pinned URLs (`https://github.com/actions/checkout@v4`,
`https://github.com/actions/setup-dotnet@v4`) per CLAUDE.md §8.7 and §15 — Gitea `https://github.com/actions/setup-dotnet@v4`) per CLAUDE.md §8.7 and §15 — Gitea
Actions resolves them from GitHub. Actions resolves them from GitHub.
> **`compose-smoke` runs on a containerized runner.** Workspace bind mounts do > **`compose-smoke` runs on a containerized runner.** Workspace bind mounts do
> **not** reach the sibling containers Compose starts, so config/assets are baked > **not** reach the sibling containers Compose starts, so config/assets are
> into derived images instead of being mounted. If you add a service that needs a > streamed into external named volumes via `docker cp` (`infra/seed-config.sh`),
> repo file at runtime, bake it — don't bind-mount it. See > and the upstream images are used verbatim (no build). If you add a service that
> needs a repo file at runtime, seed it the same way — don't bind-mount it. Note:
> bare `docker compose up` no longer self-seeds; use `make up`. See
> [gitea-actions-gotchas.md](gitea-actions-gotchas.md). > [gitea-actions-gotchas.md](gitea-actions-gotchas.md).
## Running CI locally (`make ci`) ## Running CI locally (`make ci`)

View File

@@ -38,39 +38,50 @@ 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` self-hosted `respellion-linux` setup), which is why switching to `ubuntu-latest`
exposed it. exposed it.
**Fix: bake assets into derived images instead of bind-mounting them.** Anything **Fix: the upstream images are used verbatim (no build); config is streamed into
a Compose service needs at runtime that lives in the repo is `COPY`-ed into a external named volumes with `docker cp`.** `infra/seed-config.sh` creates a fixed-
small derived image, so it is present regardless of where the daemon runs. We use name volume per asset, runs a throwaway helper container that mounts it, and
**`build.dockerfile_inline`** — the 2-line recipe lives in the compose file, so `docker cp`s the files in. `docker cp` streams bytes over the Docker API, so it
there are no standalone `Dockerfile` artifacts to maintain: works no matter where the daemon runs (including Docker-in-Docker). The services
then mount those volumes:
| Asset | Derived image | Built by | | Asset | External volume | Mounted by → at |
|---|---|---| |---|---|---|
| OpenZaak `setup_configuration/data.yaml` | `register-referentie/openzaak:dev` | `oz-init` `dockerfile_inline` | | OpenZaak `setup_configuration/data.yaml` | `rr-oz-config` | `oz-init``/app/setup_configuration` |
| Keycloak realm exports | `register-referentie/keycloak:dev` | `keycloak` `dockerfile_inline` | | Keycloak realm exports | `rr-kc-realms` | `keycloak``/opt/keycloak/data/import` |
| `workflows/registratie.bpmn` | `register-referentie/flowable-init:dev` | `flowable-init` `dockerfile_inline` | | `workflows/registratie.bpmn` | `rr-fl-bpmn` | `flowable-init` `/work` |
The base image tag is interpolated by Compose (`${OPENZAAK_TAG}`) so pinning is The volumes are declared `external: true` with fixed `name:`s so they resolve
unchanged. OpenZaak's `openzaak`/`oz-celery` reuse the one image `oz-init` builds. identically under docker compose and podman-compose. The seed step (`make` runs
Open Notificaties needs **no** bake — `nrc-init` runs migrations only, so all NRC it before every `up`) recreates them fresh each time; `make down` / the
services use the plain base image. per-service `*-down` targets remove them. Open Notificaties needs no config at all
`nrc-init` runs migrations only.
`dockerfile_inline` works on both the CI runner (docker compose) and local **Two hard constraints drove this design:**
**podman-compose**, unlike `docker cp`-into-volumes (which needs `docker compose
create`, a subcommand podman-compose lacks). - Use plain `docker volume create` / `docker run` / `docker cp`**not**
`docker compose create`, which **podman-compose** (the local dev runtime) does
not implement.
- `docker cp` rather 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.** **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 inline `content`* — Compose materialises these as a - *Compose `configs:` with inline `content`* — Compose materialises these as a
temp file on the **client** side and bind-mounts it, so it hits the exact same temp file on the **client** side and bind-mounts it → same daemon-can't-see-it
daemon-can't-see-the-path problem. problem.
- *A self-hosted runner that runs jobs on the host* — works, but reintroduces a - *A self-hosted runner that runs jobs on the host* — bind mounts would then work
bespoke runner and undoes the move to the hosted `ubuntu-latest` label. with zero seeding, but it reintroduces a bespoke runner and undoes the move to
the hosted `ubuntu-latest` label.
**Consequence for local dev.** There is now no bind mount of these config files, No bind mounts of these config files remain, so the SELinux `:z`/`:Z` relabel flag
so the SELinux `:z`/`:Z` relabel flag is no longer needed anywhere in `infra/`, is no longer needed anywhere in `infra/` (named volumes don't need relabeling).
and rootless Podman no longer needs the files to be world-readable. One mechanism
(build) works on both Podman locally and Docker-in-Docker in CI.
## `--wait` fails on one-shot containers with no dependant ## `--wait` fails on one-shot containers with no dependant

View File

@@ -73,7 +73,10 @@ The Makefile auto-points `DOCKER_HOST` at the Podman socket when it exists, so t
resultaattypen — beyond the lean seed). List with `?status=alles`. resultaattypen — beyond the lean seed). List with `?status=alles`.
- **Image tag.** Pinned to `openzaak/open-zaak:1.28.2` via `${OPENZAAK_TAG}` (bump - **Image tag.** Pinned to `openzaak/open-zaak:1.28.2` via `${OPENZAAK_TAG}` (bump
deliberately, not via `:latest`). deliberately, not via `:latest`).
- **Config is baked, not mounted.** `setup_configuration/data.yaml` is `COPY`-ed into - **Config arrives via a volume, not a bind mount.** `setup_configuration/data.yaml`
a derived image via `oz-init`'s inline Dockerfile (`build.dockerfile_inline` in is streamed into the external `rr-oz-config` volume by `infra/seed-config.sh`
the compose) rather than bind-mounted, so the init container finds it on Gitea's (`docker cp`) and mounted at `/app/setup_configuration`, so the init container
containerized CI runner too. See [gitea-actions-gotchas.md](gitea-actions-gotchas.md). finds it on Gitea's containerized CI runner too (bind mounts don't reach sibling
containers there). The image is the upstream `openzaak/open-zaak` verbatim — no
build. Run via `make openzaak-up` (seeds first). See
[gitea-actions-gotchas.md](gitea-actions-gotchas.md).

View File

@@ -47,17 +47,7 @@ services:
networks: [cg] networks: [cg]
oz-init: oz-init:
# Derived image: base OpenZaak + the setup_configuration YAML baked in. We use image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
# an inline Dockerfile (not a separate file, not a bind mount) because bind
# mounts don't reach sibling containers on the containerized CI runner.
# The ${OPENZAAK_TAG} below is interpolated by Compose. See
# docs/runbooks/gitea-actions-gotchas.md.
build:
context: ./openzaak
dockerfile_inline: |
FROM docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
COPY setup_configuration /app/setup_configuration
image: register-referentie/openzaak:dev
environment: &oz-env environment: &oz-env
DJANGO_SETTINGS_MODULE: openzaak.conf.docker DJANGO_SETTINGS_MODULE: openzaak.conf.docker
SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production} SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production}
@@ -78,6 +68,10 @@ services:
OPENZAAK_SUPERUSER_EMAIL: admin@localhost OPENZAAK_SUPERUSER_EMAIL: admin@localhost
RUN_SETUP_CONFIG: "true" RUN_SETUP_CONFIG: "true"
command: /setup_configuration.sh command: /setup_configuration.sh
# data.yaml is streamed into this external volume by infra/seed-config.sh
# before start (bind mounts don't reach sibling containers on the CI runner).
volumes:
- oz-config:/app/setup_configuration:ro
depends_on: depends_on:
oz-db: oz-db:
condition: service_healthy condition: service_healthy
@@ -86,7 +80,7 @@ services:
networks: [cg] networks: [cg]
openzaak: openzaak:
image: register-referentie/openzaak:dev image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
environment: *oz-env environment: *oz-env
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import requests,sys; sys.exit(0 if requests.head('http://localhost:8000/admin/').status_code in (200,302) else 1)"] test: ["CMD", "python", "-c", "import requests,sys; sys.exit(0 if requests.head('http://localhost:8000/admin/').status_code in (200,302) else 1)"]
@@ -102,7 +96,7 @@ services:
networks: [cg] networks: [cg]
oz-celery: oz-celery:
image: register-referentie/openzaak:dev image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
environment: *oz-env environment: *oz-env
command: /celery_worker.sh command: /celery_worker.sh
depends_on: depends_on:
@@ -193,14 +187,7 @@ services:
# ── Keycloak (S-02) ────────────────────────────────────────────────────── # ── Keycloak (S-02) ──────────────────────────────────────────────────────
keycloak: keycloak:
# Derived image: base Keycloak + the realm exports baked into the import dir image: quay.io/keycloak/keycloak:26.1
# (inline Dockerfile, no bind mount — see docs/runbooks/gitea-actions-gotchas.md).
build:
context: ./keycloak
dockerfile_inline: |
FROM quay.io/keycloak/keycloak:26.1
COPY realms /opt/keycloak/data/import
image: register-referentie/keycloak:dev
command: ["start-dev", "--import-realm"] command: ["start-dev", "--import-realm"]
environment: environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_USERNAME: admin
@@ -211,6 +198,9 @@ services:
KC_HTTP_ENABLED: "true" KC_HTTP_ENABLED: "true"
ports: ports:
- "8180:8080" - "8180:8080"
# realm exports are streamed into this external volume by infra/seed-config.sh.
volumes:
- kc-realms:/opt/keycloak/data/import:ro
networks: [cg] networks: [cg]
# ── Flowable (S-03) ────────────────────────────────────────────────────── # ── Flowable (S-03) ──────────────────────────────────────────────────────
@@ -244,16 +234,11 @@ services:
networks: [cg] networks: [cg]
flowable-init: flowable-init:
# The BPMN is baked into a tiny curl image (build context = repo-root image: docker.io/curlimages/curl:latest
# workflows/) instead of bind-mounted, so the deploy works on Gitea's
# containerized runner too. See docs/runbooks/gitea-actions-gotchas.md.
build:
context: ../workflows
dockerfile_inline: |
FROM docker.io/curlimages/curl:latest
COPY registratie.bpmn /work/registratie.bpmn
image: register-referentie/flowable-init:dev
restart: "no" restart: "no"
# registratie.bpmn is streamed into this external volume by infra/seed-config.sh.
volumes:
- fl-bpmn:/work:ro
command: command:
- sh - sh
- -c - -c
@@ -318,6 +303,18 @@ volumes:
oz-db: oz-db:
nrc-db: nrc-db:
flowable-db: flowable-db:
# Config volumes — created and populated out-of-band by infra/seed-config.sh
# (docker cp), because bind mounts don't reach sibling containers on the CI
# runner. `external` keeps the names deterministic; the seed step manages them.
oz-config:
external: true
name: rr-oz-config
kc-realms:
external: true
name: rr-kc-realms
fl-bpmn:
external: true
name: rr-fl-bpmn
networks: networks:
cg: cg:

View File

@@ -38,16 +38,11 @@ services:
# Deploys workflows/registratie.bpmn via the REST API once flowable-rest is up. # Deploys workflows/registratie.bpmn via the REST API once flowable-rest is up.
# Idempotent: skips if a deployment named "registratie" already exists. # Idempotent: skips if a deployment named "registratie" already exists.
flowable-init: flowable-init:
# The BPMN is baked into a tiny curl image (build context = repo-root image: docker.io/curlimages/curl:latest
# workflows/) instead of bind-mounted, so the deploy works on Gitea's
# containerized runner too. See docs/runbooks/gitea-actions-gotchas.md.
build:
context: ../../workflows
dockerfile_inline: |
FROM docker.io/curlimages/curl:latest
COPY registratie.bpmn /work/registratie.bpmn
image: register-referentie/flowable-init:dev
restart: "no" restart: "no"
# registratie.bpmn is streamed into this external volume by infra/seed-config.sh.
volumes:
- fl-bpmn:/work:ro
command: command:
- sh - sh
- -c - -c
@@ -66,6 +61,10 @@ services:
volumes: volumes:
flowable-db: flowable-db:
# populated out-of-band by infra/seed-config.sh (docker cp) — see that script.
fl-bpmn:
external: true
name: rr-fl-bpmn
networks: networks:
cg: cg:

View File

@@ -9,14 +9,7 @@
# Admin console: http://localhost:8180/ (admin / admin — dev only) # Admin console: http://localhost:8180/ (admin / admin — dev only)
services: services:
keycloak: keycloak:
# Derived image: base Keycloak + realm exports baked into the import dir via an image: quay.io/keycloak/keycloak:26.1
# inline Dockerfile. See docs/runbooks/gitea-actions-gotchas.md.
build:
context: .
dockerfile_inline: |
FROM quay.io/keycloak/keycloak:26.1
COPY realms /opt/keycloak/data/import
image: register-referentie/keycloak:dev
command: ["start-dev", "--import-realm"] command: ["start-dev", "--import-realm"]
environment: environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_USERNAME: admin
@@ -28,7 +21,15 @@ services:
KC_HTTP_ENABLED: "true" KC_HTTP_ENABLED: "true"
ports: ports:
- "8180:8080" - "8180:8080"
# realm exports are streamed into this external volume by infra/seed-config.sh.
volumes:
- kc-realms:/opt/keycloak/data/import:ro
networks: [cg] networks: [cg]
volumes:
kc-realms:
external: true
name: rr-kc-realms
networks: networks:
cg: cg:

View File

@@ -32,14 +32,7 @@ services:
networks: [cg] networks: [cg]
oz-init: oz-init:
# Derived image: base OpenZaak + setup_configuration baked in via an inline image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
# Dockerfile. See docs/runbooks/gitea-actions-gotchas.md for why not a mount.
build:
context: .
dockerfile_inline: |
FROM docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
COPY setup_configuration /app/setup_configuration
image: register-referentie/openzaak:dev
environment: &oz-env environment: &oz-env
DJANGO_SETTINGS_MODULE: openzaak.conf.docker DJANGO_SETTINGS_MODULE: openzaak.conf.docker
SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production} SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production}
@@ -62,6 +55,9 @@ services:
OPENZAAK_SUPERUSER_EMAIL: admin@localhost OPENZAAK_SUPERUSER_EMAIL: admin@localhost
RUN_SETUP_CONFIG: "true" RUN_SETUP_CONFIG: "true"
command: /setup_configuration.sh command: /setup_configuration.sh
# data.yaml is streamed into this external volume by infra/seed-config.sh.
volumes:
- oz-config:/app/setup_configuration:ro
depends_on: depends_on:
oz-db: oz-db:
condition: service_healthy condition: service_healthy
@@ -70,7 +66,7 @@ services:
networks: [cg] networks: [cg]
openzaak: openzaak:
image: register-referentie/openzaak:dev image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
environment: *oz-env environment: *oz-env
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import requests,sys; sys.exit(0 if requests.head('http://localhost:8000/admin/').status_code in (200,302) else 1)"] test: ["CMD", "python", "-c", "import requests,sys; sys.exit(0 if requests.head('http://localhost:8000/admin/').status_code in (200,302) else 1)"]
@@ -86,7 +82,7 @@ services:
networks: [cg] networks: [cg]
oz-celery: oz-celery:
image: register-referentie/openzaak:dev image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2}
environment: *oz-env environment: *oz-env
command: /celery_worker.sh command: /celery_worker.sh
depends_on: depends_on:
@@ -96,6 +92,10 @@ services:
volumes: volumes:
oz-db: oz-db:
# populated out-of-band by infra/seed-config.sh (docker cp) — see that script.
oz-config:
external: true
name: rr-oz-config
networks: networks:
cg: cg:

45
infra/seed-config.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#
# Populate the external named *config* volumes that the upstream services mount,
# by `docker cp`-ing files into a throwaway helper container that mounts each one.
#
# Why: the compose stack uses the upstream images verbatim (no build). On Gitea's
# containerized runner, `docker compose` starts the stack as SIBLING containers
# via the host daemon, so a workspace bind mount resolves to a path the daemon
# can't see and is mounted empty. `docker cp` instead streams bytes over the
# Docker API, so the files reach the volume regardless of where the daemon runs.
# We use plain docker primitives (volume create / run / cp / rm) rather than
# `docker compose create`, because podman-compose (local dev) lacks that
# subcommand. Fixed-name `external` volumes keep the names deterministic across
# both runtimes. See docs/runbooks/gitea-actions-gotchas.md.
#
# Usage: seed-config.sh <key> [<key> ...] where key ∈ { oz, kc, fl }
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPER="${SEED_HELPER_IMAGE:-docker.io/library/busybox:stable}"
populate() { # volume source(file or dir/.)
local vol="$1" src="$2" cid
docker volume rm -f "$vol" >/dev/null 2>&1 || true
docker volume create "$vol" >/dev/null
# A *created* (never started) helper is enough: the volume is attached at create
# time, `docker cp` writes through to it, and `docker rm` is instant (nothing to
# stop). `docker create` is a container subcommand both docker and podman have —
# unlike `docker compose create`, which podman-compose lacks.
cid="$(docker create -v "$vol:/dest" "$HELPER" true)"
docker cp "$src" "$cid:/dest/"
docker rm "$cid" >/dev/null
echo " seeded $vol"
}
[ "$#" -gt 0 ] || { echo "usage: seed-config.sh <oz|kc|fl> ..." >&2; exit 2; }
for key in "$@"; do
case "$key" in
oz) populate rr-oz-config "$here/openzaak/setup_configuration/." ;;
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
fl) populate rr-fl-bpmn "$here/../workflows/registratie.bpmn" ;;
*) echo "unknown seed key: $key" >&2; exit 2 ;;
esac
done