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
+19 -11
View File
@@ -97,19 +97,28 @@ cd apps/portal-bff && pnpm exec prisma generate && cd ../..
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
# .env from .env.example, then fill in DATABASE_URL with your local Postgres
cp apps/portal-bff/.env.example apps/portal-bff/.env
# 1. Configure infra secrets (copy template, edit, do not commit).
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)
docker run -d --name apf-postgres -p 5432:5432 \
-e POSTGRES_USER=portal -e POSTGRES_PASSWORD=portal -e POSTGRES_DB=portal_dev \
postgres:17-alpine
# 2. Bring up the core stack (postgres + redis + otel-collector).
docker compose -f infra/local/dev.compose.yml up -d
# 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.
| 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. |
| **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)). |
| **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)). |
| **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/`. |
+64 -5
View File
@@ -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).
| 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) |
| 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`) | — |
| 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:
- **`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).
---
@@ -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
These are listed here so a contributor knows where to expect related files; they don't exist yet.
| 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) |
| `runbooks/*.md` | Operational runbooks (incident response, secret rotation, runner upgrade procedure, …) | First incident, or when ops cadence justifies them |
+28
View File
@@ -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
+145
View File
@@ -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
+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;
+65
View File
@@ -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