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.
This commit is contained in:
+19
-11
@@ -97,19 +97,28 @@ cd apps/portal-bff && pnpm exec prisma generate && cd ../..
|
|||||||
pnpm nx run-many -t lint test build
|
pnpm nx run-many -t lint test build
|
||||||
```
|
```
|
||||||
|
|
||||||
For the BFF to actually run end-to-end, you'll also need:
|
For the BFF to actually run end-to-end, you'll also need the local infrastructure stack — Postgres, Redis, OpenTelemetry Collector — provisioned via Docker Compose:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env from .env.example, then fill in DATABASE_URL with your local Postgres
|
# 1. Configure infra secrets (copy template, edit, do not commit).
|
||||||
cp apps/portal-bff/.env.example apps/portal-bff/.env
|
cp infra/local/.env.example infra/local/.env
|
||||||
|
$EDITOR infra/local/.env
|
||||||
|
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD.
|
||||||
|
|
||||||
# Local Postgres via Docker (one-liner; will be replaced by a Docker Compose file in a later doc)
|
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
||||||
docker run -d --name apf-postgres -p 5432:5432 \
|
docker compose -f infra/local/dev.compose.yml up -d
|
||||||
-e POSTGRES_USER=portal -e POSTGRES_PASSWORD=portal -e POSTGRES_DB=portal_dev \
|
|
||||||
postgres:17-alpine
|
# 3. (Optional) Activate viewers when debugging:
|
||||||
|
docker compose -f infra/local/dev.compose.yml --profile dbtools up -d # pgweb
|
||||||
|
docker compose -f infra/local/dev.compose.yml --profile observability up -d # Jaeger UI
|
||||||
|
|
||||||
|
# 4. App-side env from .env.example, then fill in DATABASE_URL pointing
|
||||||
|
# to the compose-managed Postgres (matches the values you set in
|
||||||
|
# infra/local/.env).
|
||||||
|
cp apps/portal-bff/.env.example apps/portal-bff/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
A proper local-dev infra spec (Postgres HA-like, Redis, OTel collector) will land with the on-prem infrastructure ADR; in the meantime the one-liner above is sufficient to run the BFF.
|
Full reference for the local stack — service inventory, port table, persistence, bootstrap re-run procedure — lives in [`infra/README.md`](../infra/README.md) → "Local-dev stack".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -351,12 +360,11 @@ This doc starts as a phase-1 + cross-cutting reference. As features for later ph
|
|||||||
When a section grows beyond a short subsection, it is extracted to its own file under `docs/development/`. Per the documentation convention (see [README.md](README.md)), we group into a folder once we have at least three related files; this doc is then re-organised into an index pointing at the extracted files. Until then, all sections live here.
|
When a section grows beyond a short subsection, it is extracted to its own file under `docs/development/`. Per the documentation convention (see [README.md](README.md)), we group into a folder once we have at least three related files; this doc is then re-organised into an index pointing at the extracted files. Until then, all sections live here.
|
||||||
|
|
||||||
| Future section | Phase | Triggered by |
|
| Future section | Phase | Triggered by |
|
||||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Local infra recipe** — Docker Compose for Postgres, Redis, OTel Collector, and a Postgres-friendly viewer. | 2 / 3b | First feature that needs Redis (sessions, ADR-0010). Earlier if the BFF gains a meaningful amount of business code. |
|
|
||||||
| **Auth dev-loop** — Microsoft 365 Developer tenant configuration, MSAL Node connection, OIDC code-flow walkthrough, switching between dev and prod-like tenants. | 2 | Auth flow code lands ([ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md)) once the dev tenant is provisioned by IT. |
|
| **Auth dev-loop** — Microsoft 365 Developer tenant configuration, MSAL Node connection, OIDC code-flow walkthrough, switching between dev and prod-like tenants. | 2 | Auth flow code lands ([ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md)) once the dev tenant is provisioned by IT. |
|
||||||
| **Session inspection** — reading the Redis session store in dev, decrypting the AES-GCM `tokens` blob with the dev key, force-logout patterns. | 2 | Sessions module lands ([ADR-0010](decisions/0010-session-management-redis.md)). |
|
| **Session inspection** — reading the Redis session store in dev, decrypting the AES-GCM `tokens` blob with the dev key, force-logout patterns. | 2 | Sessions module lands ([ADR-0010](decisions/0010-session-management-redis.md)). |
|
||||||
| **MFA step-up debugging** — triggering claims-challenge flows, verifying `mfaVerifiedAt` freshness, testing the SPA HTTP interceptor that handles 401 + claims challenge. | 2 | First `@RequireMfa()` route lands ([ADR-0011](decisions/0011-mfa-enforcement-entra-conditional-access.md)). |
|
| **MFA step-up debugging** — triggering claims-challenge flows, verifying `mfaVerifiedAt` freshness, testing the SPA HTTP interceptor that handles 401 + claims challenge. | 2 | First `@RequireMfa()` route lands ([ADR-0011](decisions/0011-mfa-enforcement-entra-conditional-access.md)). |
|
||||||
| **Observability dev-loop** — running an OTel collector container locally, viewing traces (Tempo / Jaeger UI), reading Pino logs with `pino-pretty`, correlating front spans to BFF spans via `traceparent`. | 2 | OTel SDK setup lands ([ADR-0012](decisions/0012-observability-pino-opentelemetry.md)); needs the local collector from "Local infra recipe". |
|
| **Observability dev-loop** — viewing traces in the Jaeger UI provisioned by `infra/local/dev.compose.yml --profile observability`, reading Pino logs with `pino-pretty`, correlating front spans to BFF spans via `traceparent`. | 2 | OTel SDK setup lands ([ADR-0012](decisions/0012-observability-pino-opentelemetry.md)). |
|
||||||
| **Audit-log inspection workflow** — querying `audit.events` as `audit_reader`, joining with app logs by `trace_id`, validating the append-only role grants in dev. | 2 | Audit module lands ([ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md)). |
|
| **Audit-log inspection workflow** — querying `audit.events` as `audit_reader`, joining with app logs by `trace_id`, validating the append-only role grants in dev. | 2 | Audit module lands ([ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md)). |
|
||||||
| **Downstream API integration recipe** — adding a new `DownstreamApiConfig`, choosing the auth strategy (OBO vs service+assertion), wiring resilience policies, testing with a mocked downstream. | 2 | First downstream client lands ([ADR-0014](decisions/0014-downstream-api-access-obo-pattern.md)). |
|
| **Downstream API integration recipe** — adding a new `DownstreamApiConfig`, choosing the auth strategy (OBO vs service+assertion), wiring resilience policies, testing with a mocked downstream. | 2 | First downstream client lands ([ADR-0014](decisions/0014-downstream-api-access-obo-pattern.md)). |
|
||||||
| **Component patterns library** — the in-house, spartan-style components (Angular CDK + Tailwind) as they ship, with a11y notes per component (keyboard model, ARIA, screen-reader expectations). | 5b suite | First non-placeholder component in `libs/shared/ui/`. |
|
| **Component patterns library** — the in-house, spartan-style components (Angular CDK + Tailwind) as they ship, with a11y notes per component (keyboard model, ARIA, screen-reader expectations). | 5b suite | First non-placeholder component in `libs/shared/ui/`. |
|
||||||
|
|||||||
+64
-5
@@ -3,15 +3,15 @@
|
|||||||
Infrastructure-as-code artefacts for the project. Separate from application code and from documentation: this folder contains the recipes and configs that the team and ops use to stand up running infrastructure (CI runners, future local-dev databases, future on-prem deploy assets).
|
Infrastructure-as-code artefacts for the project. Separate from application code and from documentation: this folder contains the recipes and configs that the team and ops use to stand up running infrastructure (CI runners, future local-dev databases, future on-prem deploy assets).
|
||||||
|
|
||||||
| Subject | File / Folder | ADR / Reference |
|
| Subject | File / Folder | ADR / Reference |
|
||||||
| -------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------- |
|
| -------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Self-hosted CI runners (Gitea Actions) | [`ci-runners.compose.yml`](ci-runners.compose.yml) | [ADR-0015 §"Runners"](../docs/decisions/0015-cicd-gitea-actions.md) |
|
| Self-hosted CI runners (Gitea Actions) | [`ci-runners.compose.yml`](ci-runners.compose.yml) | [ADR-0015 §"Runners"](../docs/decisions/0015-cicd-gitea-actions.md) |
|
||||||
| Shared `act_runner` configuration | [`runner-config.yaml`](runner-config.yaml) | [ADR-0015 §"Runners"](../docs/decisions/0015-cicd-gitea-actions.md) |
|
| Shared `act_runner` configuration | [`runner-config.yaml`](runner-config.yaml) | [ADR-0015 §"Runners"](../docs/decisions/0015-cicd-gitea-actions.md) |
|
||||||
| Runtime state of the runners | `data/` (git-ignored after `.gitignore`) | — |
|
| Runtime state of the runners | `data/` (git-ignored after `.gitignore`) | — |
|
||||||
| Env-vars template for the runners | `.env.example` (`.env` is git-ignored) | — |
|
| Env-vars template for the runners | `.env.example` (`.env` is git-ignored) | — |
|
||||||
|
| Local-dev runtime stack | [`local/`](local/) | [ADR-0006](../docs/decisions/0006-persistence-postgresql-prisma.md), [ADR-0010](../docs/decisions/0010-session-management-redis.md), [ADR-0012](../docs/decisions/0012-observability-pino-opentelemetry.md), [ADR-0013](../docs/decisions/0013-audit-trail-separated-postgres-append-only.md) |
|
||||||
|
|
||||||
Future folders / files that will land here as the corresponding ADRs ship:
|
Future folders / files that will land here as the corresponding ADRs ship:
|
||||||
|
|
||||||
- **`local/`** — Docker Compose for the developer's machine (Postgres + Redis + OTel collector). Triggered by the first feature that needs Redis (sessions, ADR-0010).
|
|
||||||
- **`prod/`** — On-prem deploy manifests (HA Postgres, Redis Sentinel, OTel collector + backend, secret manager). Triggered by the on-prem infrastructure ADR (phase 3b).
|
- **`prod/`** — On-prem deploy manifests (HA Postgres, Redis Sentinel, OTel collector + backend, secret manager). Triggered by the on-prem infrastructure ADR (phase 3b).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -121,13 +121,72 @@ Old, no-longer-referenced images can be reaped during the periodic `docker syste
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Local-dev stack — `local/`
|
||||||
|
|
||||||
|
A Docker Compose recipe spinning up the runtime services the BFF and ADRs assume — Postgres, Redis, OpenTelemetry Collector — plus optional viewers (pgweb, Jaeger UI) gated behind Compose profiles. Designed to start in a single command on a contributor's WSL2 / Linux / macOS host.
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||||
|
| [`local/dev.compose.yml`](local/dev.compose.yml) | Service definitions: postgres, redis, otel-collector, plus pgweb and jaeger behind profiles |
|
||||||
|
| [`local/.env.example`](local/.env.example) | Credentials + ports template (copy to `.env`, which is git-ignored) |
|
||||||
|
| [`local/init/postgres/01-init.sql`](local/init/postgres/01-init.sql) | Bootstrap SQL for ADR-0013: audit roles + schema, applied on first boot only |
|
||||||
|
| [`local/otel-collector.yaml`](local/otel-collector.yaml) | Collector pipeline: OTLP receivers → batch → debug exporter (always) + forward to Jaeger when active |
|
||||||
|
|
||||||
|
### First-time setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd infra/local
|
||||||
|
|
||||||
|
# 1. Configure local secrets (copy template, edit, do not commit).
|
||||||
|
cp .env.example .env
|
||||||
|
$EDITOR .env
|
||||||
|
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD
|
||||||
|
# (defaults in the template are placeholders that the compose
|
||||||
|
# rejects with `must be set in infra/local/.env` if left as-is).
|
||||||
|
|
||||||
|
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
||||||
|
docker compose -f dev.compose.yml up -d
|
||||||
|
|
||||||
|
# 3. (Optional) Activate viewers when needed:
|
||||||
|
docker compose -f dev.compose.yml --profile dbtools up -d # pgweb
|
||||||
|
docker compose -f dev.compose.yml --profile observability up -d # Jaeger UI
|
||||||
|
docker compose -f dev.compose.yml --profile dbtools --profile observability up -d # both
|
||||||
|
|
||||||
|
# 4. Verify health.
|
||||||
|
docker compose -f dev.compose.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service endpoints (defaults)
|
||||||
|
|
||||||
|
| Service | Host port | Purpose |
|
||||||
|
| ------------------- | --------- | ------------------------------------------------------------------- |
|
||||||
|
| Postgres | 5432 | DB connection — `postgres://portal:<pwd>@localhost:5432/portal_dev` |
|
||||||
|
| Redis | 6379 | Sessions, OBO cache (per ADR-0010 / ADR-0014) |
|
||||||
|
| OTel Collector gRPC | 4317 | `OTEL_EXPORTER_OTLP_ENDPOINT` for the BFF and the SPA |
|
||||||
|
| OTel Collector HTTP | 4318 | OTLP/HTTP variant |
|
||||||
|
| pgweb (profile) | 8081 | http://localhost:8081 — Postgres GUI |
|
||||||
|
| Jaeger UI (profile) | 16686 | http://localhost:16686 — trace explorer |
|
||||||
|
|
||||||
|
All ports are overridable via `.env` if the host machine has conflicts.
|
||||||
|
|
||||||
|
### Operational tips
|
||||||
|
|
||||||
|
- **Persistence** — state lives in named Docker volumes (`apf-portal-postgres-data`, `apf-portal-redis-data`). Survives `docker compose down`. Use `docker compose -f dev.compose.yml down -v` to wipe (also wipes the audit-roles bootstrap, which re-runs on the next fresh boot).
|
||||||
|
- **Bootstrap re-run** — the SQL in `local/init/postgres/` only runs on a **fresh** Postgres data volume. To replay after editing the file, `down -v` (loses all dev data) or run the SQL manually with `docker compose exec postgres psql -U portal -d portal_dev -f /docker-entrypoint-initdb.d/01-init.sql`.
|
||||||
|
- **Logs** — `docker compose -f dev.compose.yml logs -f <service>` to follow a single service. `otel-collector` is the loudest — its `debug` exporter prints every span / metric / log it receives.
|
||||||
|
- **Image upgrades** — same policy as the runner image (deliberate, not via `:latest`). Renovate's docker-compose manager will surface bumps automatically once the dashboard rule allows them.
|
||||||
|
|
||||||
|
### Production parity
|
||||||
|
|
||||||
|
This stack is **dev-only**. The corresponding production layout (HA Postgres, Redis Sentinel cluster, OTel Collector with a real backend, secret manager) lives in the future on-prem-infrastructure ADR — see `prod/` placeholder below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Future infra concerns — placeholders
|
## Future infra concerns — placeholders
|
||||||
|
|
||||||
These are listed here so a contributor knows where to expect related files; they don't exist yet.
|
These are listed here so a contributor knows where to expect related files; they don't exist yet.
|
||||||
|
|
||||||
| File | Purpose | Triggered by |
|
| File | Purpose | Triggered by |
|
||||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
| --------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||||
| `local/dev.compose.yml` | Postgres 17 + Redis (Sentinel-flavoured single-node) + OTel collector for local dev | First feature that needs Redis or end-to-end OTel traces |
|
|
||||||
| `local/init/postgres/*.sql` | Bootstrap SQL to create the Postgres roles `audit_owner` / `audit_writer` / `audit_reader` / `audit_archiver` (per ADR-0013) and provision the dev DB | Same as above |
|
|
||||||
| `prod/*` | On-prem deployment manifests (k8s, Compose, or whatever the on-prem infra ADR settles on) | The on-prem infrastructure ADR (phase 3b) |
|
| `prod/*` | On-prem deployment manifests (k8s, Compose, or whatever the on-prem infra ADR settles on) | The on-prem infrastructure ADR (phase 3b) |
|
||||||
| `runbooks/*.md` | Operational runbooks (incident response, secret rotation, runner upgrade procedure, …) | First incident, or when ops cadence justifies them |
|
| `runbooks/*.md` | Operational runbooks (incident response, secret rotation, runner upgrade procedure, …) | First incident, or when ops cadence justifies them |
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Local-dev secrets and ports for `infra/local/dev.compose.yml`.
|
||||||
|
# Copy to `.env` (which is git-ignored) and adjust as needed.
|
||||||
|
#
|
||||||
|
# cp .env.example .env
|
||||||
|
# $EDITOR .env
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Postgres
|
||||||
|
# `POSTGRES_PASSWORD` is mandatory — the compose refuses to boot without it.
|
||||||
|
POSTGRES_USER=portal
|
||||||
|
POSTGRES_PASSWORD=portal_dev_change_me
|
||||||
|
POSTGRES_DB=portal_dev
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Redis
|
||||||
|
# Same — mandatory.
|
||||||
|
REDIS_PASSWORD=redis_dev_change_me
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- OTel
|
||||||
|
OTEL_GRPC_PORT=4317
|
||||||
|
OTEL_HTTP_PORT=4318
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Optional viewers
|
||||||
|
# Only consumed when the matching Compose profile is activated:
|
||||||
|
# --profile dbtools → pgweb
|
||||||
|
# --profile observability → Jaeger UI
|
||||||
|
PGWEB_PORT=8081
|
||||||
|
JAEGER_UI_PORT=16686
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# 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
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# OpenTelemetry Collector config for the local-dev stack.
|
||||||
|
#
|
||||||
|
# v1 scope (per ADR-0012):
|
||||||
|
# - Accept OTLP from the BFF and the SPA on gRPC (4317) and HTTP
|
||||||
|
# (4318). The default OTEL_EXPORTER_OTLP_ENDPOINT in dev points
|
||||||
|
# here.
|
||||||
|
# - Batch + log everything to stdout via the `debug` exporter — the
|
||||||
|
# dev sees what their app emits without standing up a backend.
|
||||||
|
# - When the `observability` Compose profile is active, additionally
|
||||||
|
# forward to Jaeger (`jaeger:4317` on the internal Compose
|
||||||
|
# network). When the profile is inactive, the OTLP→Jaeger export
|
||||||
|
# fails at warn level but does not impact the debug pipeline.
|
||||||
|
#
|
||||||
|
# Production replaces the debug exporter with the proper backend
|
||||||
|
# (chosen in the future on-prem infrastructure ADR — likely Tempo +
|
||||||
|
# Loki + Mimir, or an OpenTelemetry-friendly all-in-one).
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
otlp:
|
||||||
|
protocols:
|
||||||
|
grpc:
|
||||||
|
endpoint: 0.0.0.0:4317
|
||||||
|
http:
|
||||||
|
endpoint: 0.0.0.0:4318
|
||||||
|
|
||||||
|
processors:
|
||||||
|
batch:
|
||||||
|
# Small batches in dev so the developer sees output quickly.
|
||||||
|
timeout: 5s
|
||||||
|
send_batch_size: 100
|
||||||
|
|
||||||
|
exporters:
|
||||||
|
debug:
|
||||||
|
verbosity: detailed
|
||||||
|
otlp/jaeger:
|
||||||
|
endpoint: jaeger:4317
|
||||||
|
tls:
|
||||||
|
insecure: true
|
||||||
|
sending_queue:
|
||||||
|
enabled: true
|
||||||
|
# When jaeger isn't running (profile off), retries fall back
|
||||||
|
# to a small queue rather than blocking the pipeline.
|
||||||
|
queue_size: 100
|
||||||
|
retry_on_failure:
|
||||||
|
enabled: true
|
||||||
|
initial_interval: 5s
|
||||||
|
max_interval: 30s
|
||||||
|
|
||||||
|
service:
|
||||||
|
pipelines:
|
||||||
|
traces:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [debug, otlp/jaeger]
|
||||||
|
logs:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [debug]
|
||||||
|
metrics:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [debug]
|
||||||
|
telemetry:
|
||||||
|
logs:
|
||||||
|
level: info
|
||||||
Reference in New Issue
Block a user