From b349dff49642feff5962ffa29b93639f8711c451 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Thu, 25 Jun 2026 10:22:14 +0200 Subject: [PATCH] refactor(infra): use upstream images verbatim, seed config via docker cp (refs #30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Makefile | 46 +++++++++++++++----- docs/runbooks/ci.md | 10 +++-- docs/runbooks/gitea-actions-gotchas.md | 59 +++++++++++++++----------- docs/runbooks/openzaak.md | 11 +++-- infra/docker-compose.yml | 57 ++++++++++++------------- infra/flowable/docker-compose.yml | 17 ++++---- infra/keycloak/docker-compose.yml | 17 ++++---- infra/openzaak/docker-compose.yml | 20 ++++----- infra/seed-config.sh | 45 ++++++++++++++++++++ 9 files changed, 182 insertions(+), 100 deletions(-) create mode 100755 infra/seed-config.sh diff --git a/Makefile b/Makefile index 4606bb1..b3908f3 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,14 @@ COMPOSE := infra/docker-compose.yml # 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. 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_BASE := http://localhost:8000 NRC_COMPOSE := infra/opennotificaties/docker-compose.yml @@ -32,7 +40,7 @@ export DOCKER_HOST := unix://$(PODMAN_SOCK) 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: lint build unit smoke @@ -49,19 +57,28 @@ build: unit: dotnet test $(SLN) -c Release -## smoke: bring the whole stack up, wait for the health-checked services, tear down -# Step 1 starts EVERYTHING (incl. one-shot init jobs that deploy and exit 0). -# Step 2 waits only for the durable, health-checked services ($(WAIT_SVCS)) — see -# WAIT_SVCS above for why the one-shots are excluded. The healthchecks run inside -# the containers, so this needs no host port access (the CI runner can't reach -# published ports anyway). +## smoke: seed config, bring the whole stack up, wait for health-checked services, tear down +# SEED populates the external config volumes first (the upstream images are used +# verbatim — no build for them). `up -d --build` then starts EVERYTHING (building +# only our acl/bff). The second `up --wait` waits for the durable, health-checked +# services ($(WAIT_SVCS)); the one-shots are excluded because `--wait` fails when a +# one-shot with no `service_completed_successfully` dependant exits (flowable-init). +# Healthchecks run inside the containers, so no host port access is needed. smoke: + $(SEED) oz kc fl 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: docker compose -f $(COMPOSE) down --volumes + -docker volume rm -f $(CFG_VOLS) ## changelog: regenerate CHANGELOG.md from Conventional Commits (git-cliff) changelog: @@ -69,11 +86,11 @@ changelog: ## openzaak-up: start the OpenZaak stack (migrations run on first start) openzaak-up: + $(SEED) oz docker compose -f $(OZ_COMPOSE) up -d ## openzaak-smoke: start OpenZaak, then assert it is up with auth enforced -openzaak-smoke: - docker compose -f $(OZ_COMPOSE) up -d +openzaak-smoke: openzaak-up @bash -c 'set -e; \ echo "waiting for OpenZaak to respond..."; \ 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: 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: + $(SEED) oz docker compose $(STACK_FILES) up -d ## 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: docker compose $(STACK_FILES) down --volumes + -docker volume rm -f rr-oz-config ## keycloak-up: start Keycloak with the four imported realms keycloak-up: + $(SEED) kc docker compose -f $(KC_COMPOSE) up -d ## 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: 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: + $(SEED) fl docker compose -f $(FL_COMPOSE) up -d ## 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: docker compose -f $(FL_COMPOSE) down --volumes + -docker volume rm -f rr-fl-bpmn ## help: list available targets help: diff --git a/docs/runbooks/ci.md b/docs/runbooks/ci.md index 107b985..f6445d5 100644 --- a/docs/runbooks/ci.md +++ b/docs/runbooks/ci.md @@ -16,16 +16,18 @@ and CI cannot drift: | `lint` | `make lint` → `dotnet format … --verify-no-changes` | .NET 10 SDK | | `build` | `make build` → `dotnet build … -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`, `https://github.com/actions/setup-dotnet@v4`) per CLAUDE.md §8.7 and §15 — Gitea Actions resolves them from GitHub. > **`compose-smoke` runs on a containerized runner.** Workspace bind mounts do -> **not** reach the sibling containers Compose starts, so config/assets are baked -> into derived images instead of being mounted. If you add a service that needs a -> repo file at runtime, bake it — don't bind-mount it. See +> **not** reach the sibling containers Compose starts, so config/assets are +> streamed into external named volumes via `docker cp` (`infra/seed-config.sh`), +> 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). ## Running CI locally (`make ci`) diff --git a/docs/runbooks/gitea-actions-gotchas.md b/docs/runbooks/gitea-actions-gotchas.md index 7d8bd31..42e5bc0 100644 --- a/docs/runbooks/gitea-actions-gotchas.md +++ b/docs/runbooks/gitea-actions-gotchas.md @@ -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` exposed it. -**Fix: bake assets into derived images instead of bind-mounting them.** Anything -a Compose service needs at runtime that lives in the repo is `COPY`-ed into a -small derived image, so it is present regardless of where the daemon runs. We use -**`build.dockerfile_inline`** — the 2-line recipe lives in the compose file, so -there are no standalone `Dockerfile` artifacts to maintain: +**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 cp`s 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 | Derived image | Built by | +| Asset | External volume | Mounted by → at | |---|---|---| -| OpenZaak `setup_configuration/data.yaml` | `register-referentie/openzaak:dev` | `oz-init` `dockerfile_inline` | -| Keycloak realm exports | `register-referentie/keycloak:dev` | `keycloak` `dockerfile_inline` | -| `workflows/registratie.bpmn` | `register-referentie/flowable-init:dev` | `flowable-init` `dockerfile_inline` | +| 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 base image tag is interpolated by Compose (`${OPENZAAK_TAG}`) so pinning is -unchanged. OpenZaak's `openzaak`/`oz-celery` reuse the one image `oz-init` builds. -Open Notificaties needs **no** bake — `nrc-init` runs migrations only, so all NRC -services use the plain base image. +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. -`dockerfile_inline` works on both the CI runner (docker compose) and local -**podman-compose**, unlike `docker cp`-into-volumes (which needs `docker compose -create`, a subcommand podman-compose lacks). +**Two hard constraints drove this design:** + +- 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 -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 inline `content`* — Compose materialises these as a - temp file on the **client** side and bind-mounts it, so it hits the exact same - daemon-can't-see-the-path problem. -- *A self-hosted runner that runs jobs on the host* — works, but reintroduces a - bespoke runner and undoes the move to the hosted `ubuntu-latest` label. + 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-latest` label. -**Consequence for local dev.** There is now no bind mount of these config files, -so the SELinux `:z`/`:Z` relabel flag is no longer needed anywhere in `infra/`, -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. +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 diff --git a/docs/runbooks/openzaak.md b/docs/runbooks/openzaak.md index 7196d2a..b054416 100644 --- a/docs/runbooks/openzaak.md +++ b/docs/runbooks/openzaak.md @@ -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`. - **Image tag.** Pinned to `openzaak/open-zaak:1.28.2` via `${OPENZAAK_TAG}` (bump deliberately, not via `:latest`). -- **Config is baked, not mounted.** `setup_configuration/data.yaml` is `COPY`-ed into - a derived image via `oz-init`'s inline Dockerfile (`build.dockerfile_inline` in - the compose) rather than bind-mounted, so the init container finds it on Gitea's - containerized CI runner too. See [gitea-actions-gotchas.md](gitea-actions-gotchas.md). +- **Config arrives via a volume, not a bind mount.** `setup_configuration/data.yaml` + is streamed into the external `rr-oz-config` volume by `infra/seed-config.sh` + (`docker cp`) and mounted at `/app/setup_configuration`, so the init container + 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). diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index b63f7be..b138c6a 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -47,17 +47,7 @@ services: networks: [cg] oz-init: - # Derived image: base OpenZaak + the setup_configuration YAML baked in. We use - # 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 + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: &oz-env DJANGO_SETTINGS_MODULE: openzaak.conf.docker SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production} @@ -78,6 +68,10 @@ services: OPENZAAK_SUPERUSER_EMAIL: admin@localhost RUN_SETUP_CONFIG: "true" 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: oz-db: condition: service_healthy @@ -86,7 +80,7 @@ services: networks: [cg] openzaak: - image: register-referentie/openzaak:dev + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: *oz-env 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)"] @@ -102,7 +96,7 @@ services: networks: [cg] oz-celery: - image: register-referentie/openzaak:dev + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: *oz-env command: /celery_worker.sh depends_on: @@ -193,14 +187,7 @@ services: # ── Keycloak (S-02) ────────────────────────────────────────────────────── keycloak: - # Derived image: base Keycloak + the realm exports baked into the import dir - # (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 + image: quay.io/keycloak/keycloak:26.1 command: ["start-dev", "--import-realm"] environment: KC_BOOTSTRAP_ADMIN_USERNAME: admin @@ -211,6 +198,9 @@ services: KC_HTTP_ENABLED: "true" ports: - "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] # ── Flowable (S-03) ────────────────────────────────────────────────────── @@ -244,16 +234,11 @@ services: networks: [cg] flowable-init: - # The BPMN is baked into a tiny curl image (build context = repo-root - # 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 + image: docker.io/curlimages/curl:latest restart: "no" + # registratie.bpmn is streamed into this external volume by infra/seed-config.sh. + volumes: + - fl-bpmn:/work:ro command: - sh - -c @@ -318,6 +303,18 @@ volumes: oz-db: nrc-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: cg: diff --git a/infra/flowable/docker-compose.yml b/infra/flowable/docker-compose.yml index c66caeb..f1921b6 100644 --- a/infra/flowable/docker-compose.yml +++ b/infra/flowable/docker-compose.yml @@ -38,16 +38,11 @@ services: # Deploys workflows/registratie.bpmn via the REST API once flowable-rest is up. # Idempotent: skips if a deployment named "registratie" already exists. flowable-init: - # The BPMN is baked into a tiny curl image (build context = repo-root - # 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 + image: docker.io/curlimages/curl:latest restart: "no" + # registratie.bpmn is streamed into this external volume by infra/seed-config.sh. + volumes: + - fl-bpmn:/work:ro command: - sh - -c @@ -66,6 +61,10 @@ services: volumes: 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: cg: diff --git a/infra/keycloak/docker-compose.yml b/infra/keycloak/docker-compose.yml index 7d74e0c..678b553 100644 --- a/infra/keycloak/docker-compose.yml +++ b/infra/keycloak/docker-compose.yml @@ -9,14 +9,7 @@ # Admin console: http://localhost:8180/ (admin / admin — dev only) services: keycloak: - # Derived image: base Keycloak + realm exports baked into the import dir via an - # 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 + image: quay.io/keycloak/keycloak:26.1 command: ["start-dev", "--import-realm"] environment: KC_BOOTSTRAP_ADMIN_USERNAME: admin @@ -28,7 +21,15 @@ services: KC_HTTP_ENABLED: "true" ports: - "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] +volumes: + kc-realms: + external: true + name: rr-kc-realms + networks: cg: diff --git a/infra/openzaak/docker-compose.yml b/infra/openzaak/docker-compose.yml index c3c5174..9f9e50a 100644 --- a/infra/openzaak/docker-compose.yml +++ b/infra/openzaak/docker-compose.yml @@ -32,14 +32,7 @@ services: networks: [cg] oz-init: - # Derived image: base OpenZaak + setup_configuration baked in via an inline - # 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 + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: &oz-env DJANGO_SETTINGS_MODULE: openzaak.conf.docker SECRET_KEY: ${OZ_SECRET_KEY:-dev-only-not-for-production} @@ -62,6 +55,9 @@ services: OPENZAAK_SUPERUSER_EMAIL: admin@localhost RUN_SETUP_CONFIG: "true" 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: oz-db: condition: service_healthy @@ -70,7 +66,7 @@ services: networks: [cg] openzaak: - image: register-referentie/openzaak:dev + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: *oz-env 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)"] @@ -86,7 +82,7 @@ services: networks: [cg] oz-celery: - image: register-referentie/openzaak:dev + image: docker.io/openzaak/open-zaak:${OPENZAAK_TAG:-1.28.2} environment: *oz-env command: /celery_worker.sh depends_on: @@ -96,6 +92,10 @@ services: volumes: 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: cg: diff --git a/infra/seed-config.sh b/infra/seed-config.sh new file mode 100755 index 0000000..64d2993 --- /dev/null +++ b/infra/seed-config.sh @@ -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 [ ...] 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 ..." >&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