`public.domain` is the whole switch. Empty — the default, and what compose, CI and a laptop cluster use — renders nothing new and leaves every manifest as it was. Set it and templates/edge.yaml adds a Caddy deployment that gets its own certificates from Let's Encrypt and proxies the five browser-facing hostnames to the ClusterIP services, so a public deployment doesn't use their NodePorts at all. Caddy rather than an ingress controller because the four portals already run caddy:2-alpine (ADR-0034, whose ceiling note called exactly this out): no new dependency, no cert-manager, no CRDs, no Ingress objects for five hostnames that never change. The Fedora host keeps only a layer-4 forward of 80/443, because the public IP is there and nothing in the cluster can claim it. KC_HOSTNAME and the portals' config.json now both come from `big.keycloakUrl`, so the issuer a token carries and the authority the BFF discovers are one string by construction (ADR-0010) rather than by two templates agreeing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
635 lines
27 KiB
YAML
635 lines
27 KiB
YAML
# Values for the BIG reference stack on Kubernetes.
|
|
#
|
|
# `workloads` is a near-literal transcription of infra/docker-compose.yml — same
|
|
# service names, same images, same env, same one-shots — so the two stacks can be
|
|
# diffed by eye. Read that file's comments for the *why* behind each setting; only
|
|
# the deviations forced by Kubernetes are re-explained here.
|
|
#
|
|
# Every env value is rendered with Helm's `tpl`, so it may use:
|
|
# {{ .Release.Namespace }} — for a cluster-internal FQDN
|
|
# {{ .Values.host }} — the node address a browser reaches the cluster on
|
|
#
|
|
# Deviations from compose, all of them consequences of the platform:
|
|
# * The compose stack hands the ACL and the seeds OpenZaak's *container IP*,
|
|
# because OpenZaak and NRC validate URLs with Django's URLValidator and a
|
|
# single-label host ("openzaak") is rejected. In Kubernetes the service FQDN
|
|
# (openzaak.<ns>.svc.cluster.local) is already multi-label, so the IP dance and
|
|
# the `objecten.local` network alias both disappear.
|
|
# * `depends_on: service_healthy` becomes a `waitFor` init container (TCP wait)
|
|
# plus readiness probes. Ordering is otherwise not enforced: every bootstrap
|
|
# job is idempotent and Kubernetes retries.
|
|
# * The published ports are NodePorts (see `nodePorts`), not host ports.
|
|
|
|
# The address a browser outside the cluster uses to reach the node: your Talos
|
|
# VM's IP. It pins Keycloak's issuer and the portals' OIDC authority to one
|
|
# string, so browser tokens and the BFF's discovered issuer agree.
|
|
host: 192.168.122.100
|
|
|
|
# Set when pulling from a private registry (e.g. the Gitea Container Registry).
|
|
imagePullSecrets: []
|
|
|
|
images:
|
|
# Where the images built from THIS repo live. Empty = the bare
|
|
# `register-referentie/<svc>:dev` names, which only works if the node already
|
|
# has them. On Talos it never does — point this at a registry the node can
|
|
# reach (see docs/runbooks/kubernetes-talos.md).
|
|
registry: ""
|
|
repositoryPrefix: register-referentie
|
|
tag: dev
|
|
# Applies to this repo's images only (see _helpers.tpl). Always, because `dev`
|
|
# is a mutable tag: with IfNotPresent the node keeps the first image it pulled
|
|
# and `make k8s-images` would appear to do nothing. The registry is in-cluster,
|
|
# so a re-pull is local and cheap — but the pods do depend on it being up.
|
|
pullPolicy: Always
|
|
busybox: docker.io/library/busybox:stable
|
|
|
|
persistence:
|
|
# Empty = every database is an emptyDir, so the stack comes up on a bare
|
|
# cluster with no CSI driver. Set to a StorageClass (e.g. `local-path`) to keep
|
|
# the data across pod restarts.
|
|
storageClass: ""
|
|
|
|
# The public TLS edge (ADR-0035). Empty `domain` = no edge at all: nothing in
|
|
# templates/edge.yaml is rendered and the stack is reached on the NodePorts below,
|
|
# with `host` above pinning the OIDC origin.
|
|
#
|
|
# Set it and an in-cluster Caddy terminates TLS for `<sub>.<domain>`, gets its own
|
|
# certificates from Let's Encrypt and proxies to the ClusterIP services. The node
|
|
# only has to be reachable on the two NodePorts here — the Fedora host forwards
|
|
# 80/443 to them (see docs/runbooks/kubernetes-talos.md).
|
|
public:
|
|
domain: ""
|
|
# ACME registration address; Let's Encrypt uses it for expiry warnings.
|
|
email: ""
|
|
image: docker.io/library/caddy:2-alpine
|
|
# <subdomain>: <in-cluster service:port>. `auth` is not free-form — big.keycloakUrl
|
|
# builds the pinned issuer from it.
|
|
routes:
|
|
register: openbaar:80
|
|
mijn: self-service:80
|
|
behandel: behandel:80
|
|
beheer: beheer:80
|
|
auth: keycloak:8080
|
|
# Where the host's 80/443 forward lands. Not 30080/30443: 30080 is the BFF.
|
|
nodePorts:
|
|
http: 32080
|
|
https: 32443
|
|
|
|
# The only place a port is published outside the cluster. A workload listed here
|
|
# gets a NodePort on its single port; everything else stays ClusterIP.
|
|
nodePorts:
|
|
openzaak: 30000
|
|
nrc-web: 30001
|
|
objecttypen: 30020
|
|
objecten: 30021
|
|
bff: 30080
|
|
flowable-rest: 30090
|
|
self-service: 30140
|
|
openbaar: 30141
|
|
behandel: 30142
|
|
beheer: 30143
|
|
keycloak: 30180
|
|
grafana: 30300
|
|
|
|
# ── Shared env blocks (the compose YAML anchors) ────────────────────────────────
|
|
envGroups:
|
|
|
|
oz:
|
|
UWSGI_PROCESSES: "1"
|
|
UWSGI_THREADS: "2"
|
|
DJANGO_SETTINGS_MODULE: openzaak.conf.docker
|
|
SECRET_KEY: dev-only-not-for-production
|
|
DB_HOST: oz-db
|
|
DB_NAME: openzaak
|
|
DB_USER: openzaak
|
|
DB_PASSWORD: openzaak
|
|
IS_HTTPS: "no"
|
|
ALLOWED_HOSTS: "*"
|
|
CACHE_DEFAULT: oz-redis:6379/0
|
|
CACHE_AXES: oz-redis:6379/0
|
|
CELERY_BROKER_URL: redis://oz-redis:6379/1
|
|
CELERY_RESULT_BACKEND: redis://oz-redis:6379/1
|
|
DISABLE_2FA: "true"
|
|
NOTIFICATIONS_DISABLED: "false"
|
|
OPENZAAK_SUPERUSER_USERNAME: admin
|
|
DJANGO_SUPERUSER_PASSWORD: admin
|
|
OPENZAAK_SUPERUSER_EMAIL: admin@localhost
|
|
RUN_SETUP_CONFIG: "true"
|
|
|
|
nrc:
|
|
UWSGI_PROCESSES: "1"
|
|
UWSGI_THREADS: "2"
|
|
DJANGO_SETTINGS_MODULE: nrc.conf.docker
|
|
SECRET_KEY: dev-only-not-for-production
|
|
DB_HOST: nrc-db
|
|
DB_NAME: opennotificaties
|
|
DB_USER: opennotificaties
|
|
DB_PASSWORD: opennotificaties
|
|
IS_HTTPS: "no"
|
|
ALLOWED_HOSTS: "*"
|
|
CACHE_DEFAULT: nrc-redis:6379/0
|
|
CACHE_AXES: nrc-redis:6379/0
|
|
CELERY_BROKER_URL: redis://nrc-redis:6379/1
|
|
CELERY_RESULT_BACKEND: redis://nrc-redis:6379/1
|
|
DISABLE_2FA: "true"
|
|
OPENNOTIFICATIES_SUPERUSER_USERNAME: admin
|
|
DJANGO_SUPERUSER_PASSWORD: admin
|
|
OPENNOTIFICATIES_SUPERUSER_EMAIL: admin@localhost
|
|
RUN_SETUP_CONFIG: "true"
|
|
NOTIFICATION_SEC_INTERVAL: "5"
|
|
|
|
objecttypen:
|
|
UWSGI_PROCESSES: "1"
|
|
UWSGI_THREADS: "2"
|
|
DJANGO_SETTINGS_MODULE: objecttypes.conf.docker
|
|
SECRET_KEY: dev-only-not-for-production
|
|
DB_HOST: objecttypen-db
|
|
DB_NAME: objecttypes
|
|
DB_USER: objecttypes
|
|
DB_PASSWORD: objecttypes
|
|
ALLOWED_HOSTS: "*"
|
|
CACHE_DEFAULT: objecttypen-redis:6379/0
|
|
CACHE_AXES: objecttypen-redis:6379/0
|
|
DISABLE_2FA: "true"
|
|
OTEL_SDK_DISABLED: "true"
|
|
RUN_SETUP_CONFIG: "true"
|
|
|
|
objecten:
|
|
UWSGI_PROCESSES: "1"
|
|
UWSGI_THREADS: "2"
|
|
DJANGO_SETTINGS_MODULE: objects.conf.docker
|
|
SECRET_KEY: dev-only-not-for-production
|
|
DB_HOST: objecten-db
|
|
DB_NAME: objects
|
|
DB_USER: objects
|
|
DB_PASSWORD: objects
|
|
ALLOWED_HOSTS: "*"
|
|
CACHE_DEFAULT: objecten-redis:6379/0
|
|
CACHE_AXES: objecten-redis:6379/0
|
|
DISABLE_2FA: "true"
|
|
OTEL_SDK_DISABLED: "true"
|
|
CELERY_BROKER_URL: redis://objecten-redis:6379/1
|
|
CELERY_RESULT_BACKEND: redis://objecten-redis:6379/1
|
|
NOTIFICATIONS_DISABLED: "false"
|
|
RUN_SETUP_CONFIG: "true"
|
|
|
|
# Traces for the .NET services. Always set, like compose: the exporter fails
|
|
# harmlessly when Tempo is absent (services/*/Program.cs).
|
|
otel:
|
|
OTEL_EXPORTER_OTLP_ENDPOINT: http://tempo:4317
|
|
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
|
|
|
|
# ── Workloads ──────────────────────────────────────────────────────────────────
|
|
# Per entry: image | own (built here) · args · envFrom (env groups) · env
|
|
# ports · probe (a literal readinessProbe) · files (ConfigMap mounts) · data
|
|
# (a database volume) · waitFor (host:port to wait for) · job · enabled
|
|
#
|
|
# `args` (never `command`) is the compose `command:` equivalent: compose replaces
|
|
# the image's CMD, and so does Kubernetes' `args` — Kubernetes' `command` would
|
|
# replace the ENTRYPOINT instead. The chart fails to render if you use `command`.
|
|
workloads:
|
|
|
|
# ── OpenZaak (S-01) ─────────────────────────────────────────────────────────
|
|
oz-db:
|
|
image: docker.io/postgis/postgis:17-3.5
|
|
args: [postgres, -c, max_connections=300]
|
|
env:
|
|
POSTGRES_USER: openzaak
|
|
POSTGRES_PASSWORD: openzaak
|
|
POSTGRES_DB: openzaak
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 4Gi }
|
|
probe:
|
|
exec:
|
|
command: [sh, -c, "pg_isready -U openzaak -d openzaak && psql -U openzaak -d openzaak -c 'SELECT PostGIS_Version();' -q"]
|
|
periodSeconds: 5
|
|
|
|
oz-redis:
|
|
image: docker.io/library/redis:7
|
|
ports: [{ name: redis, port: 6379 }]
|
|
probe: { tcpSocket: { port: 6379 } }
|
|
openzaak:
|
|
image: docker.io/openzaak/open-zaak:1.28.2
|
|
# setup_configuration first, then the server — in ONE container, on purpose.
|
|
# Both /setup_configuration.sh and /start.sh run `manage.py migrate`, so a
|
|
# separate init Job (as compose has, ordered by depends_on) races this pod for
|
|
# the same database and Django fails with "relation already exists".
|
|
args: [sh, -c, "/setup_configuration.sh && exec /start.sh"]
|
|
envFrom: [oz]
|
|
ports: [{ name: http, port: 8000 }]
|
|
# /admin/ answers 302 when Django is up — a redirect counts as ready.
|
|
probe:
|
|
httpGet: { path: /admin/, port: 8000 }
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
failureThreshold: 30
|
|
files: [{ configMap: rr-oz-config, mountPath: /app/setup_configuration }]
|
|
waitFor: [oz-db:5432, oz-redis:6379]
|
|
|
|
oz-celery:
|
|
image: docker.io/openzaak/open-zaak:1.28.2
|
|
args: [/celery_worker.sh]
|
|
envFrom: [oz]
|
|
waitFor: [oz-db:5432, oz-redis:6379]
|
|
|
|
# ── Open Notificaties / NRC (S-01-c) ────────────────────────────────────────
|
|
nrc-db:
|
|
image: docker.io/postgis/postgis:17-3.5
|
|
args: [postgres, -c, max_connections=300]
|
|
env:
|
|
POSTGRES_USER: opennotificaties
|
|
POSTGRES_PASSWORD: opennotificaties
|
|
POSTGRES_DB: opennotificaties
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 2Gi }
|
|
probe:
|
|
exec: { command: [pg_isready, -U, opennotificaties, -d, opennotificaties] }
|
|
periodSeconds: 5
|
|
|
|
nrc-redis:
|
|
image: docker.io/library/redis:7
|
|
ports: [{ name: redis, port: 6379 }]
|
|
probe: { tcpSocket: { port: 6379 } }
|
|
nrc-web:
|
|
image: docker.io/openzaak/open-notificaties:1.16.1
|
|
# setup_configuration first, then the server — in ONE container, on purpose.
|
|
# Both /setup_configuration.sh and /start.sh run `manage.py migrate`, so a
|
|
# separate init Job (as compose has, ordered by depends_on) races this pod for
|
|
# the same database and Django fails with "relation already exists".
|
|
args: [sh, -c, "/setup_configuration.sh && exec /start.sh"]
|
|
envFrom: [nrc]
|
|
ports: [{ name: http, port: 8000 }]
|
|
probe:
|
|
httpGet: { path: /admin/, port: 8000 }
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
failureThreshold: 30
|
|
files: [{ configMap: rr-nrc-config, mountPath: /app/setup_configuration }]
|
|
waitFor: [nrc-db:5432, nrc-redis:6379, openzaak:8000]
|
|
|
|
nrc-celery:
|
|
image: docker.io/openzaak/open-notificaties:1.16.1
|
|
args: [/celery_worker.sh]
|
|
envFrom: [nrc]
|
|
waitFor: [nrc-db:5432, nrc-redis:6379]
|
|
|
|
# Without beat, notifications are accepted but never delivered (ADR-0007).
|
|
nrc-beat:
|
|
image: docker.io/openzaak/open-notificaties:1.16.1
|
|
args: [/celery_beat.sh]
|
|
envFrom: [nrc]
|
|
waitFor: [nrc-db:5432, nrc-redis:6379]
|
|
|
|
# ── Keycloak (S-02) ─────────────────────────────────────────────────────────
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:26.1
|
|
args: [start-dev, --import-realm]
|
|
env:
|
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
|
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
|
KEYCLOAK_ADMIN: admin
|
|
KEYCLOAK_ADMIN_PASSWORD: admin
|
|
KC_HEALTH_ENABLED: "true"
|
|
KC_HTTP_ENABLED: "true"
|
|
# Pin the issuer to the address the browser uses, and let backchannel calls
|
|
# keep using keycloak:8080 — the BFF discovers metadata in-cluster and gets
|
|
# this issuer back, which is what browser tokens carry (infra/host-browser.yml).
|
|
KC_HOSTNAME: '{{ include "big.keycloakUrl" . }}'
|
|
KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
|
|
ports: [{ name: http, port: 8080 }]
|
|
# TCP, not /health/ready on the management port: nothing here gates on realm
|
|
# import, and a wrong health path would leave the Service with no endpoints.
|
|
probe: { tcpSocket: { port: 8080 }, initialDelaySeconds: 15 }
|
|
files: [{ configMap: rr-kc-realms, mountPath: /opt/keycloak/data/import }]
|
|
|
|
# ── Flowable (S-03) ─────────────────────────────────────────────────────────
|
|
flowable-db:
|
|
image: docker.io/library/postgres:16
|
|
env:
|
|
POSTGRES_USER: flowable
|
|
POSTGRES_PASSWORD: flowable
|
|
POSTGRES_DB: flowable
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 2Gi }
|
|
probe:
|
|
exec: { command: [pg_isready, -U, flowable, -d, flowable] }
|
|
periodSeconds: 5
|
|
|
|
flowable-rest:
|
|
image: docker.io/flowable/flowable-rest:latest
|
|
env:
|
|
SPRING_DATASOURCE_DRIVER-CLASS-NAME: org.postgresql.Driver
|
|
SPRING_DATASOURCE_URL: jdbc:postgresql://flowable-db:5432/flowable
|
|
SPRING_DATASOURCE_USERNAME: flowable
|
|
SPRING_DATASOURCE_PASSWORD: flowable
|
|
ports: [{ name: http, port: 8080 }]
|
|
# Every REST path needs basic auth, so an httpGet probe would read 401 as
|
|
# not-ready. TCP is the honest signal here.
|
|
probe: { tcpSocket: { port: 8080 }, initialDelaySeconds: 20 }
|
|
waitFor: [flowable-db:5432]
|
|
|
|
# Deploys the BPMN to the process engine and the DMN to the DMN engine as two
|
|
# separate deployments — flowable-rest does not cascade one into the other
|
|
# (S-13, ADR-0016). Idempotent.
|
|
flowable-init:
|
|
job: true
|
|
image: docker.io/curlimages/curl:latest
|
|
args:
|
|
- sh
|
|
- -c
|
|
- |
|
|
svc=http://flowable-rest:8080/flowable-rest/service/repository/deployments
|
|
dmn=http://flowable-rest:8080/flowable-rest/dmn-api/dmn-repository/deployments
|
|
until curl -sf -u rest-admin:test "$svc" >/dev/null 2>&1; do echo "waiting for flowable-rest..."; sleep 3; done
|
|
if curl -s -u rest-admin:test "$dmn" | grep -q '"name":"diploma-eligibility.dmn"'; then
|
|
echo "diploma-eligibility DMN already deployed; skip"
|
|
else
|
|
curl -sf -u rest-admin:test -F 'file=@/work/diploma-eligibility.dmn;filename=diploma-eligibility.dmn' "$dmn" >/dev/null && echo "deployed diploma-eligibility DMN"
|
|
fi
|
|
if curl -s -u rest-admin:test "$svc?name=registratie" | grep -q '"name":"registratie"'; then
|
|
echo "registratie BPMN already deployed; skip"
|
|
else
|
|
curl -sf -u rest-admin:test -F 'file=@/work/registratie.bpmn;filename=registratie.bpmn' "$svc" >/dev/null && echo "deployed registratie BPMN"
|
|
fi
|
|
files: [{ configMap: rr-fl-bpmn, mountPath: /work }]
|
|
waitFor: [flowable-rest:8080]
|
|
|
|
# ── ACL ─────────────────────────────────────────────────────────────────────
|
|
acl:
|
|
own: true
|
|
envFrom: [otel]
|
|
env:
|
|
OTEL_SERVICE_NAME: acl
|
|
# The FQDN, not `openzaak`: OpenZaak rejects a single-label host on
|
|
# zaak-create. It must be the same host the zaaktype was seeded through
|
|
# (see the seed-zaaktype job) so the URLs stay host-consistent (ADR-0009).
|
|
Acl__OpenZaak__BaseUrl: "http://openzaak.{{ .Release.Namespace }}.svc.cluster.local:8000/"
|
|
Acl__OpenZaak__ClientId: big-reference-seed
|
|
Acl__OpenZaak__Secret: insecure-dev-secret-change-me
|
|
Acl__Defaults__Bronorganisatie: "517439943"
|
|
Acl__Defaults__VerantwoordelijkeOrganisatie: "517439943"
|
|
Acl__Defaults__Vertrouwelijkheidaanduiding: openbaar
|
|
Acl__Defaults__ZaaktypeIdentificatie: BIG-REGISTRATIE
|
|
Acl__Defaults__InformatieobjecttypeOmschrijving: Diploma
|
|
# Objecten reflects the request Host into the object url it returns, and
|
|
# publishes that url to NRC — which rejects a single-label host. The FQDN
|
|
# replaces compose's `objecten.local` alias (ADR-0029).
|
|
Acl__Objecten__BaseUrl: "http://objecten.{{ .Release.Namespace }}.svc.cluster.local:8000/"
|
|
Acl__Objecten__Token: 1234567890abcdef1234567890abcdef12345678
|
|
# Short name on purpose: Objecten only accepts an objecttype URL that
|
|
# matches the one it was configured with (infra/objecten/setup_configuration
|
|
# /data.yaml → http://objecttypen:8000/api/v2/).
|
|
Acl__Objecten__ObjecttypenBaseUrl: http://objecttypen:8000/
|
|
Acl__Objecten__ObjecttypenToken: 0123456789abcdef0123456789abcdef01234567
|
|
Acl__Objecten__ObjecttypeName: RegisterRecord
|
|
ports: [{ name: http, port: 8080 }]
|
|
probe: { httpGet: { path: /health, port: 8080 }, periodSeconds: 5 }
|
|
|
|
# ── BIG Domain Service (S-05) ───────────────────────────────────────────────
|
|
domain:
|
|
own: true
|
|
envFrom: [otel]
|
|
env:
|
|
OTEL_SERVICE_NAME: domain
|
|
Flowable__BaseUrl: http://flowable-rest:8080/flowable-rest/
|
|
Flowable__Username: rest-admin
|
|
Flowable__Password: test
|
|
Acl__BaseUrl: http://acl:8080/
|
|
ports: [{ name: http, port: 8080 }]
|
|
probe: { httpGet: { path: /health, port: 8080 }, periodSeconds: 5 }
|
|
|
|
# ── BFF ─────────────────────────────────────────────────────────────────────
|
|
bff:
|
|
own: true
|
|
envFrom: [otel]
|
|
env:
|
|
OTEL_SERVICE_NAME: bff
|
|
# In-cluster authority: Keycloak's discovery document returns the pinned
|
|
# KC_HOSTNAME issuer, which is what browser tokens carry (ADR-0010).
|
|
Keycloak__Authority: http://keycloak:8080/realms/digid
|
|
Keycloak__MedewerkerAuthority: http://keycloak:8080/realms/medewerker
|
|
Downstream__Domain__BaseUrl: http://domain:8080/
|
|
Downstream__Projection__BaseUrl: http://projection-api:8080/
|
|
Downstream__Acl__BaseUrl: http://acl:8080/
|
|
ports: [{ name: http, port: 8080 }]
|
|
probe: { httpGet: { path: /health, port: 8080 }, periodSeconds: 5 }
|
|
|
|
# ── Read projection (S-06) ──────────────────────────────────────────────────
|
|
projection-db:
|
|
image: docker.io/library/postgres:16
|
|
env:
|
|
POSTGRES_USER: projection
|
|
POSTGRES_PASSWORD: projection
|
|
POSTGRES_DB: projection
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 2Gi }
|
|
probe:
|
|
exec: { command: [pg_isready, -U, projection, -d, projection] }
|
|
periodSeconds: 5
|
|
|
|
event-subscriber:
|
|
own: true
|
|
envFrom: [otel]
|
|
env:
|
|
OTEL_SERVICE_NAME: event-subscriber
|
|
ConnectionStrings__Projection: Host=projection-db;Database=projection;Username=projection;Password=projection
|
|
Acl__BaseUrl: http://acl:8080/
|
|
EventSubscriber__Webhook__AuthToken: Bearer big-reference-notifications
|
|
ports: [{ name: http, port: 8080 }]
|
|
probe: { httpGet: { path: /health, port: 8080 }, periodSeconds: 5 }
|
|
# It migrates the projection schema on start and throws if the DB is absent.
|
|
waitFor: [projection-db:5432]
|
|
|
|
projection-api:
|
|
own: true
|
|
envFrom: [otel]
|
|
env:
|
|
OTEL_SERVICE_NAME: projection-api
|
|
ConnectionStrings__Projection: Host=projection-db;Database=projection;Username=projection;Password=projection
|
|
ports: [{ name: http, port: 8080 }]
|
|
probe: { httpGet: { path: /health, port: 8080 }, periodSeconds: 5 }
|
|
waitFor: [projection-db:5432]
|
|
|
|
# ── Portals (S-08/S-09/S-12/S-15) ───────────────────────────────────────────
|
|
# Caddy serves the Angular app and reverse-proxies its endpoint group to
|
|
# http://bff:8080 — hence the Service must stay named `bff`. Caddy resolves that
|
|
# name through the system resolver, so the DNS search domains apply and no
|
|
# upstream rewriting is needed here (ADR-0034).
|
|
self-service:
|
|
own: true
|
|
ports: [{ name: http, port: 80 }]
|
|
probe: { httpGet: { path: /, port: 80 }, periodSeconds: 5 }
|
|
files:
|
|
- configMap: portal-config-digid
|
|
mountPath: /usr/share/caddy/config.json
|
|
subPath: config.json
|
|
|
|
openbaar:
|
|
own: true
|
|
ports: [{ name: http, port: 80 }]
|
|
probe: { httpGet: { path: /, port: 80 }, periodSeconds: 5 }
|
|
|
|
behandel:
|
|
own: true
|
|
ports: [{ name: http, port: 80 }]
|
|
probe: { httpGet: { path: /, port: 80 }, periodSeconds: 5 }
|
|
files:
|
|
- configMap: portal-config-medewerker
|
|
mountPath: /usr/share/caddy/config.json
|
|
subPath: config.json
|
|
|
|
beheer:
|
|
own: true
|
|
ports: [{ name: http, port: 80 }]
|
|
probe: { httpGet: { path: /, port: 80 }, periodSeconds: 5 }
|
|
files:
|
|
- configMap: portal-config-medewerker
|
|
mountPath: /usr/share/caddy/config.json
|
|
subPath: config.json
|
|
|
|
# ── Objecttypen API (S-18a) ─────────────────────────────────────────────────
|
|
objecttypen-db:
|
|
image: docker.io/library/postgres:17-alpine
|
|
env:
|
|
POSTGRES_USER: objecttypes
|
|
POSTGRES_PASSWORD: objecttypes
|
|
POSTGRES_DB: objecttypes
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 2Gi }
|
|
probe:
|
|
exec: { command: [pg_isready, -U, objecttypes] }
|
|
periodSeconds: 5
|
|
|
|
objecttypen-redis:
|
|
image: docker.io/library/redis:7
|
|
ports: [{ name: redis, port: 6379 }]
|
|
probe: { tcpSocket: { port: 6379 } }
|
|
objecttypen:
|
|
image: docker.io/maykinmedia/objecttypes-api:3.4.2
|
|
# setup_configuration first, then the server — in ONE container, on purpose.
|
|
# Both /setup_configuration.sh and /start.sh run `manage.py migrate`, so a
|
|
# separate init Job (as compose has, ordered by depends_on) races this pod for
|
|
# the same database and Django fails with "relation already exists".
|
|
args: [sh, -c, "/setup_configuration.sh && exec /start.sh"]
|
|
envFrom: [objecttypen]
|
|
ports: [{ name: http, port: 8000 }]
|
|
probe:
|
|
httpGet: { path: /admin/, port: 8000 }
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
failureThreshold: 30
|
|
files: [{ configMap: rr-objecttypen-config, mountPath: /app/setup_configuration }]
|
|
waitFor: [objecttypen-db:5432, objecttypen-redis:6379]
|
|
|
|
# The RegisterRecord objecttype + published version, over the API (S-18c,
|
|
# ADR-0020/ADR-0027). The uuid is pinned — Objecten identifies it by uuid.
|
|
registerrecord-init:
|
|
job: true
|
|
image: docker.io/library/python:3-slim
|
|
args: [python, /config/register.py]
|
|
env:
|
|
OBJECTTYPEN: http://objecttypen:8000
|
|
OBJECTTYPEN_TOKEN: 0123456789abcdef0123456789abcdef01234567
|
|
SCHEMA: /config/registerrecord.schema.json
|
|
files: [{ configMap: rr-registerrecord-config, mountPath: /config }]
|
|
waitFor: [objecttypen:8000]
|
|
|
|
# ── Objecten API (S-18b) ────────────────────────────────────────────────────
|
|
objecten-db:
|
|
image: docker.io/postgis/postgis:17-3.5
|
|
env:
|
|
POSTGRES_USER: objects
|
|
POSTGRES_PASSWORD: objects
|
|
POSTGRES_DB: objects
|
|
ports: [{ name: postgres, port: 5432 }]
|
|
data: { mountPath: /var/lib/postgresql/data, size: 2Gi }
|
|
probe:
|
|
exec: { command: [pg_isready, -U, objects] }
|
|
periodSeconds: 5
|
|
|
|
objecten-redis:
|
|
image: docker.io/library/redis:7
|
|
ports: [{ name: redis, port: 6379 }]
|
|
probe: { tcpSocket: { port: 6379 } }
|
|
objecten:
|
|
image: docker.io/maykinmedia/objects-api:3.4.0
|
|
# setup_configuration first, then the server — in ONE container, on purpose.
|
|
# Both /setup_configuration.sh and /start.sh run `manage.py migrate`, so a
|
|
# separate init Job (as compose has, ordered by depends_on) races this pod for
|
|
# the same database and Django fails with "relation already exists".
|
|
args: [sh, -c, "/setup_configuration.sh && exec /start.sh"]
|
|
envFrom: [objecten]
|
|
ports: [{ name: http, port: 8000 }]
|
|
probe:
|
|
httpGet: { path: /admin/, port: 8000 }
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
failureThreshold: 30
|
|
files: [{ configMap: rr-objecten-config, mountPath: /app/setup_configuration }]
|
|
waitFor: [objecten-db:5432, objecten-redis:6379, objecttypen:8000]
|
|
|
|
# Delivers Objecten's notifications to NRC; without it every register write is
|
|
# silently undelivered (ADR-0029).
|
|
objecten-celery:
|
|
image: docker.io/maykinmedia/objects-api:3.4.0
|
|
args: [/celery_worker.sh]
|
|
envFrom: [objecten]
|
|
waitFor: [objecten-db:5432, objecten-redis:6379]
|
|
|
|
# ── Bootstrap the flow, like the local compose stack does (S-B04, ADR-0020) ──
|
|
# Seeds + publishes the BIG zaaktype through the same FQDN the ACL uses, so the
|
|
# server-assigned URLs are host-consistent. The ACL then resolves them by
|
|
# identificatie (S-27, ADR-0021) — nothing is injected back.
|
|
# Publishing validates the resultaattype against the external Selectielijst
|
|
# API, so the node needs outbound internet for this one job (ADR-0006).
|
|
seed-zaaktype:
|
|
job: true
|
|
image: docker.io/library/python:3-slim
|
|
args: [python, /seed/seed_catalogus.py]
|
|
env:
|
|
OZ_BASE: "http://openzaak.{{ .Release.Namespace }}.svc.cluster.local:8000"
|
|
OZ_PUBLISH: "1"
|
|
files: [{ configMap: rr-seed-scripts, mountPath: /seed }]
|
|
waitFor: [openzaak:8000]
|
|
|
|
# Registers the NRC abonnement on the `objecten` kanaal pointing at the
|
|
# event-subscriber, so register writes reach the projection (ADR-0030).
|
|
# Without it the openbaar register stays empty. Restart-safe and idempotent.
|
|
nrc-subscribe:
|
|
job: true
|
|
image: docker.io/library/python:3-slim
|
|
args: [python, /seed/register-abonnement.py]
|
|
env:
|
|
NRC_BASE: http://nrc-web:8000
|
|
# The script resolves this to an address for the callback URL; the FQDN
|
|
# resolves to the Service's (stable) ClusterIP, which NRC's URLValidator
|
|
# accepts — the compose stack uses the container IP for the same reason.
|
|
SINK_HOST: "event-subscriber.{{ .Release.Namespace }}.svc.cluster.local"
|
|
SINK_PORT: "8080"
|
|
SINK_AUTH: Bearer big-reference-notifications
|
|
files: [{ configMap: rr-seed-scripts, mountPath: /seed }]
|
|
waitFor: [nrc-web:8000, event-subscriber:8080]
|
|
|
|
# ── Observability backplane (S-16a, ADR-0023) ───────────────────────────────
|
|
# Off by default: these are built images too (config baked in), so switching
|
|
# them on also means pushing three more images. Enable all three together.
|
|
tempo:
|
|
enabled: false
|
|
own: true
|
|
args: ["-config.file=/etc/tempo.yaml"]
|
|
ports: [{ name: otlp, port: 4317 }, { name: http, port: 3200 }]
|
|
|
|
prometheus:
|
|
enabled: false
|
|
own: true
|
|
ports: [{ name: http, port: 9090 }]
|
|
|
|
grafana:
|
|
enabled: false
|
|
own: true
|
|
env:
|
|
GF_SECURITY_ADMIN_USER: admin
|
|
GF_SECURITY_ADMIN_PASSWORD: admin
|
|
GF_AUTH_ANONYMOUS_ENABLED: "true"
|
|
ports: [{ name: http, port: 3000 }]
|