Files
apf_portal/infra/local/dev.compose.yml
T
Julien Gautier 6123953165
CI / commits (pull_request) Successful in 1m4s
CI / check (pull_request) Successful in 1m12s
CI / scan (pull_request) Successful in 1m12s
CI / a11y (pull_request) Successful in 37s
CI / perf (pull_request) Successful in 1m29s
feat(infra): add local-dev Docker Compose stack
Bring up Postgres + Redis + OTel Collector in one command so
contributors can run the BFF end-to-end without each setting up
the runtime services manually. Replaces the throwaway
`docker run postgres:17-alpine` one-liner that was in §3 of
docs/development.md.

What lands:

- `infra/local/dev.compose.yml` — three core services
  (postgres 17.2-alpine, redis 7.4-alpine,
  otel/opentelemetry-collector-contrib 0.115.0) plus two
  viewers gated behind Compose profiles (pgweb under
  `--profile dbtools`, Jaeger 1.62 under
  `--profile observability`). All ports overridable via .env;
  state in named volumes; healthchecks on the data services.
- `infra/local/.env.example` — credentials + ports template.
  POSTGRES_PASSWORD and REDIS_PASSWORD are mandatory (compose
  refuses to boot without them); other keys default sensibly.
- `infra/local/init/postgres/01-init.sql` — bootstrap SQL per
  ADR-0013: `audit_owner` / `audit_writer` / `audit_reader` /
  `audit_archiver` roles plus `audit` schema, with default
  privileges encoding the append-only contract (INSERT to
  writer, SELECT to reader, DELETE to archiver, no UPDATE /
  TRUNCATE to anyone). Applied on first Postgres boot only;
  documented re-run procedure in infra/README.md.
- `infra/local/otel-collector.yaml` — pipeline: OTLP gRPC/HTTP
  → batch → debug exporter (always) + forward to `jaeger:4317`
  (no-op when the observability profile is off; logs warn-level
  retry but doesn't block other exporters).

Surrounding docs updated:

- `infra/README.md` — new "Local-dev stack" section with
  service inventory, port table, first-time setup, operational
  tips. The `local/` row replaces the previous placeholder.
- `docs/development.md` §3 — rewritten to walk through the
  compose-based setup; cross-links to `infra/README.md` for
  the full reference. Roadmap entry for "Local infra recipe"
  removed from §8 (now implemented); "Observability dev-loop"
  entry adjusted to point at the new Jaeger profile.

Production parity is intentionally left for the on-prem
infrastructure ADR (phase 3b) — this is the dev-only stack.
2026-05-08 18:54:22 +02:00

146 lines
4.9 KiB
YAML

# Local-dev infrastructure stack for `apf_portal`.
#
# Spins up the runtime dependencies the BFF and ADRs assume:
# - PostgreSQL 17 (per ADR-0006)
# - Redis 7 (per ADR-0010 — single node in dev,
# Sentinel HA in prod)
# - OpenTelemetry Collector (per ADR-0012 — receives OTLP from the
# BFF and the SPA, debug-logs traces in
# v1, forwards to Jaeger when activated)
#
# Optional viewers behind Compose profiles:
# - --profile dbtools → pgweb (Postgres GUI)
# - --profile observability → Jaeger UI (trace explorer)
#
# Usage from infra/local/:
# cp .env.example .env
# $EDITOR .env # set strong dev passwords
# docker compose -f dev.compose.yml up -d
#
# To bring up viewers too:
# docker compose -f dev.compose.yml --profile dbtools --profile observability up -d
#
# State persists in named volumes (`apf-portal-postgres-data`,
# `apf-portal-redis-data`). To wipe and start fresh:
# docker compose -f dev.compose.yml down -v
#
# Bootstrap SQL (audit roles + schema, per ADR-0013) is applied on
# first boot from init/postgres/. To replay after editing it, wipe
# the volume with `down -v` above.
name: apf-portal-dev
services:
postgres:
image: postgres:17.2-alpine
container_name: apf-portal-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-portal}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in infra/local/.env}
POSTGRES_DB: ${POSTGRES_DB:-portal_dev}
ports:
- '${POSTGRES_PORT:-5432}:5432'
volumes:
- postgres-data:/var/lib/postgresql/data
# Bootstrap SQL runs only on a fresh data volume.
- ./init/postgres:/docker-entrypoint-initdb.d:ro
networks:
- apf-portal-dev
healthcheck:
test:
['CMD-SHELL', 'pg_isready -U "${POSTGRES_USER:-portal}" -d "${POSTGRES_DB:-portal_dev}"']
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7.4-alpine
container_name: apf-portal-redis
restart: unless-stopped
# AOF (`--appendonly yes`) keeps sessions across container restarts —
# the dev equivalent of the persistence we want from the prod
# Sentinel cluster (per ADR-0010 §"Persistence").
command:
- redis-server
- --requirepass
- ${REDIS_PASSWORD:?REDIS_PASSWORD must be set in infra/local/.env}
- --appendonly
- 'yes'
ports:
- '${REDIS_PORT:-6379}:6379'
volumes:
- redis-data:/data
networks:
- apf-portal-dev
healthcheck:
test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'PING']
interval: 5s
timeout: 3s
retries: 5
otel-collector:
image: otel/opentelemetry-collector-contrib:0.115.0
container_name: apf-portal-otel-collector
restart: unless-stopped
command: ['--config=/etc/otel-collector.yaml']
volumes:
- ./otel-collector.yaml:/etc/otel-collector.yaml:ro
ports:
# OTLP receivers — the BFF and the SPA send here.
- '${OTEL_GRPC_PORT:-4317}:4317'
- '${OTEL_HTTP_PORT:-4318}:4318'
networks:
- apf-portal-dev
# ------------------------------------------------------------------
# Optional: Postgres GUI (`--profile dbtools`).
# pgweb is a lightweight, no-config alternative to pgAdmin.
# ------------------------------------------------------------------
pgweb:
image: sosedoff/pgweb:0.16.2
container_name: apf-portal-pgweb
restart: unless-stopped
profiles: [dbtools]
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-portal}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-portal_dev}?sslmode=disable
ports:
- '${PGWEB_PORT:-8081}:8081'
depends_on:
postgres:
condition: service_healthy
networks:
- apf-portal-dev
# ------------------------------------------------------------------
# Optional: Jaeger UI for trace exploration (`--profile observability`).
# The collector pipeline above always tries to forward to `jaeger:4317`;
# when this profile is off, those exports fail silently (logged at
# warn level by the collector — no blocking effect on the debug
# exporter that prints traces to stdout).
# OTLP ports (4317/4318) are NOT published from this service to
# avoid colliding with the collector — the collector talks to
# Jaeger on the internal Compose network.
# ------------------------------------------------------------------
jaeger:
image: jaegertracing/all-in-one:1.62
container_name: apf-portal-jaeger
restart: unless-stopped
profiles: [observability]
environment:
COLLECTOR_OTLP_ENABLED: 'true'
ports:
- '${JAEGER_UI_PORT:-16686}:16686'
networks:
- apf-portal-dev
volumes:
postgres-data:
name: apf-portal-postgres-data
redis-data:
name: apf-portal-redis-data
networks:
apf-portal-dev:
name: apf-portal-dev