ddddbc592b
pgweb 0.16 deprecated `DATABASE_URL` in favour of
`PGWEB_DATABASE_URL`. With the old name, the container starts up,
emits a `[DEPRECATION]` warning, then crashes:
Error: Invalid URL. Valid format:
postgres://user:password@host:port/db?sslmode=mode
— because the new code path reads `PGWEB_DATABASE_URL` (empty)
and the URL parser rejects an empty string.
Rename the env key in the compose file. Same value, just the new
name.
149 lines
5.1 KiB
YAML
149 lines
5.1 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.150.1
|
|
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:
|
|
# `PGWEB_DATABASE_URL` (formerly `DATABASE_URL`) — pgweb 0.16
|
|
# deprecated the old name, ignores it, and starts up empty,
|
|
# crashing with "Invalid URL".
|
|
PGWEB_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.76.0
|
|
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
|