feat(infra): add local-dev Docker Compose stack (#57)
CI / commits (push) Has been skipped
CI / check (push) Successful in 1m12s
CI / scan (push) Successful in 1m20s
CI / a11y (push) Successful in 41s
CI / perf (push) Successful in 2m9s

## Summary
Bring up Postgres + Redis + OTel Collector in one command so contributors can run the BFF end-to-end without manually wiring each service. Replaces the throwaway `docker run postgres:17-alpine` one-liner that was in `docs/development.md` §3.

### 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:
  - `--profile dbtools` → `sosedoff/pgweb:0.16.2` (Postgres GUI on port 8081)
  - `--profile observability` → `jaegertracing/all-in-one:1.62` (Jaeger UI on 16686)
  - All ports overridable via `.env`. State in named volumes. Healthchecks on 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 + `audit` schema. Default privileges encode 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.
- **`infra/local/otel-collector.yaml`** — pipeline: OTLP gRPC/HTTP → batch → debug exporter (always) + forward to `jaeger:4317`. When the observability profile is off, the Jaeger export logs warn-level retries but doesn't block the debug pipeline.

### Surrounding doc updates
- **`infra/README.md`** — new "Local-dev stack" section: service inventory, port table, first-time setup walkthrough, persistence/bootstrap-replay tips. The previous `local/` placeholder line is removed.
- **`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" line adjusted to point at the new Jaeger profile.

### Out of scope
- **Production parity** — HA Postgres, Redis Sentinel, real OTel backend (Tempo / Loki / etc.) — defer to the on-prem infrastructure ADR (phase 3b). The dev-only nature of this stack is called out explicitly in `infra/README.md`.
- **Wiring the BFF** to actually use these endpoints (NestJS config, Prisma datasource URL, OTel SDK init) — that's the **B — Observability foundations** chantier, next up.

## Test plan
- [ ] `cd infra/local && cp .env.example .env && docker compose -f dev.compose.yml up -d` → all three core services come up healthy; verify with `docker compose ps`.
- [ ] `psql postgres://portal:<pwd>@localhost:5432/portal_dev -c "\dn"` shows the `audit` schema; `\dg` shows the four audit roles.
- [ ] `redis-cli -a <pwd> PING` → `PONG`.
- [ ] Send a fake OTLP trace via grpcurl → see it printed by `docker compose logs otel-collector`.
- [ ] `--profile dbtools up -d` → http://localhost:8081 shows pgweb UI, can navigate to the audit schema.
- [ ] `--profile observability up -d` → http://localhost:16686 shows Jaeger UI; collector logs no longer report Jaeger export retries.
- [ ] `docker compose down -v` cleanly removes everything; next `up -d` re-runs the bootstrap SQL.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #57
This commit was merged in pull request #57.
This commit is contained in:
2026-05-08 19:23:43 +02:00
parent a93ee16091
commit 0f00d6d93f
6 changed files with 390 additions and 37 deletions
+48
View File
@@ -0,0 +1,48 @@
-- Bootstrap SQL for local-dev Postgres, applied on FIRST boot only
-- (Postgres image runs files in /docker-entrypoint-initdb.d once,
-- when the data volume is empty).
--
-- Implements the role + schema layout from ADR-0013 (Audit trail,
-- separated Postgres schema, append-only by Postgres role grants).
-- Production deployment manifests (future infrastructure ADR) will
-- replicate the same layout with real service accounts; in dev the
-- default `portal` user (created automatically from POSTGRES_USER)
-- is the schema owner, and the audit roles are granted to it for
-- testing role-based access patterns locally.
-- ---------------------------------------------------------------- Audit roles
-- NOLOGIN: these are permission containers, not login users. The BFF
-- connects as a login user that has been GRANTed the role.
CREATE ROLE audit_owner NOLOGIN;
CREATE ROLE audit_writer NOLOGIN;
CREATE ROLE audit_reader NOLOGIN;
CREATE ROLE audit_archiver NOLOGIN;
-- ---------------------------------------------------------------- Audit schema
-- Owned by audit_owner so DEFAULT PRIVILEGES below take effect for
-- every future table created under the schema (Prisma migration etc.).
CREATE SCHEMA audit AUTHORIZATION audit_owner;
GRANT USAGE ON SCHEMA audit TO audit_writer, audit_reader, audit_archiver;
-- ---------------------------------------------------------------- Append-only contract
-- ADR-0013 §"Append-only by role grants":
-- audit_writer → INSERT only
-- audit_reader → SELECT only
-- audit_archiver → DELETE only (used to prune past retention)
-- nobody → UPDATE, TRUNCATE
ALTER DEFAULT PRIVILEGES FOR ROLE audit_owner IN SCHEMA audit
GRANT INSERT ON TABLES TO audit_writer;
ALTER DEFAULT PRIVILEGES FOR ROLE audit_owner IN SCHEMA audit
GRANT SELECT ON TABLES TO audit_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE audit_owner IN SCHEMA audit
GRANT DELETE ON TABLES TO audit_archiver;
-- ---------------------------------------------------------------- Dev convenience
-- The default `portal` superuser bypasses these grants anyway, but
-- granting the audit roles explicitly lets us test role-based access
-- with `SET ROLE audit_writer;` etc. from a psql session against the
-- dev DB. Production never grants all four to one user.
GRANT audit_owner, audit_writer, audit_reader, audit_archiver TO portal;