## Context trails.cool runs `docker-compose.dev.yml` for local dev (PostgreSQL + BRouter) and `infrastructure/docker-compose.yml` for production (full stack with Caddy, Prometheus, Grafana, Loki, exporters). There is no staging environment. CI runs E2E tests with a manually started PostgreSQL container and a separately downloaded BRouter, not reusing the dev compose file. The gap between local dev and production causes issues: 1. Monitoring changes (alert rules, dashboards) are untestable before deploy 2. CI's PostgreSQL setup diverges from both dev and prod (no PostGIS extensions preloaded, no pg_stat_statements, no init scripts) 3. The dev PostgreSQL lacks `pg_stat_statements` which means queries that depend on it (like Grafana datasource queries) fail locally ## Goals / Non-Goals **Goals:** - Local dev PostgreSQL matches production config (pg_stat_statements, init scripts) - Optional monitoring stack available locally via a compose profile - CI E2E tests use the same compose file as local dev - One-command reset for a clean dev environment - Seed data available for both local dev and CI **Non-Goals:** - Replicating the production Caddy reverse proxy locally (apps run natively with Vite, no TLS needed for local dev) - Running S3/Garage locally (media storage is a future concern) - Federation testing (ActivityPub requires publicly reachable endpoints) - Matching exact production image versions (dev uses source builds, prod uses GHCR images) ## Decisions ### D1: Extend docker-compose.dev.yml with profiles, don't create a new file Add monitoring services to the existing `docker-compose.dev.yml` using Docker Compose profiles. The core services (postgres, brouter) have no profile assigned and always start. Monitoring services get the `monitoring` profile and only start when explicitly requested. ```bash pnpm dev:services # postgres + brouter (default) docker compose -f docker-compose.dev.yml --profile monitoring up -d # + monitoring ``` **Alternative**: Separate `docker-compose.monitoring.yml` with `extends`. Rejected — profiles are the standard Docker Compose mechanism for this, and a single file is simpler to maintain. ### D2: Service profiles — core always runs, monitoring is opt-in Two logical groups: | Profile | Services | When | |---------|----------|------| | *(none)* | postgres, brouter | Always — required for app development | | `monitoring` | prometheus, grafana, loki | Opt-in — for testing observability changes | Production-only services NOT included locally: Caddy (apps run natively), node-exporter (host metrics not useful in Docker Desktop), cadvisor (container metrics not useful locally), postgres-exporter (can add later if needed). Grafana runs with anonymous auth locally (no GitHub OAuth), connecting to the local Prometheus and Loki instances. ### D3: Database initialization — auto-push schema, seed script for test data On `pnpm dev:full`, the `scripts/dev.sh` script already runs `pnpm db:push`. Add a seed script (`scripts/seed.ts`) that inserts test data: - A test user account in Journal - A sample route with waypoints (Berlin area, matching the BRouter segment) - A sample activity linked to the route The seed script is idempotent (uses `ON CONFLICT DO NOTHING`). It runs automatically in `dev:full` but can be run standalone with `pnpm db:seed`. For CI, the seed script runs after `db:push` and before E2E tests, ensuring tests have consistent data to work with. ### D4: CI uses compose file for services Replace the manual `docker run` and BRouter download steps in `.github/ workflows/ci.yml` with: ```yaml - name: Start services run: docker compose -f docker-compose.dev.yml up -d --wait ``` The `--wait` flag blocks until health checks pass, replacing the manual `pg_isready` loops. BRouter still builds from the local Dockerfile in `docker/brouter/` and uses the same segment download mechanism. Benefits: - CI and local dev use identical service configuration - Health check logic is defined once (in compose) not twice (compose + CI) - Simpler CI workflow with fewer steps Trade-off: Docker Compose in CI adds ~5s overhead for compose parsing. The PostGIS image is already cached. BRouter segment download is already cached. Net time should be similar or faster due to parallel health checks. ### D5: dev.sh improvements — health checks, error messages, monitoring flag Improve `scripts/dev.sh`: 1. **Health check with timeout**: Use `docker compose up -d --wait` instead of manual `pg_isready` loop. This respects the healthcheck config in the compose file and has a built-in timeout. 2. **Error messages**: If Docker is not running, print a clear message instead of a cryptic error. Check for Docker before anything else. 3. **Monitoring flag**: `pnpm dev:full -- --monitoring` starts the monitoring profile alongside core services. 4. **Seed data**: Run seed script after schema push. ### D6: Environment — .env.development template with sensible defaults Create `.env.development` (gitignored) from `.env.development.example` (committed). All values have working defaults so local dev works with zero configuration: ```env DATABASE_URL=postgres://trails:trails@localhost:5432/trails BROUTER_URL=http://localhost:17777 JWT_SECRET=dev-secret-not-for-production SESSION_SECRET=dev-secret-not-for-production ``` The apps already read `DATABASE_URL` from environment. The `.env.development` file is for documentation and convenience — `scripts/dev.sh` sets these values if not already present. ## Risks / Trade-offs **[Monitoring profile adds image pulls]** → First `--profile monitoring` run downloads Prometheus, Grafana, Loki images (~500MB). Mitigation: one-time cost, cached by Docker. **[Compose in CI needs Docker Compose v2]** → GitHub Actions ubuntu-latest includes Docker Compose v2. No action needed. **[Seed data can drift from schema]** → If schema changes, seed script may break. Mitigation: seed script uses Drizzle ORM (not raw SQL), so TypeScript catches drift at compile time. **[BRouter segment download in CI]** → The compose file builds BRouter from Dockerfile but doesn't include segments. CI still needs the segment download step (cached). The compose file mounts a local directory for segments.