2026-03-01 03:10:35 +00:00
# dd0c Local Development Stack
#
# Usage: docker compose up -d
# All services share one Postgres and one Redis instance.
# Caddy handles TLS and routing for *.dd0c.localhost
services :
# --- Shared Infrastructure ---
postgres :
image : postgres:16-alpine
environment :
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
POSTGRES_USER : ${POSTGRES_USER:-dd0c}
POSTGRES_PASSWORD : ${POSTGRES_PASSWORD:-dd0c-dev}
DB_DRIFT_PASSWORD : ${DB_DRIFT_PASSWORD:-dd0c-dev}
DB_ALERT_PASSWORD : ${DB_ALERT_PASSWORD:-dd0c-dev}
DB_PORTAL_PASSWORD : ${DB_PORTAL_PASSWORD:-dd0c-dev}
DB_COST_PASSWORD : ${DB_COST_PASSWORD:-dd0c-dev}
DB_RUN_PASSWORD : ${DB_RUN_PASSWORD:-dd0c-dev}
2026-03-01 03:10:35 +00:00
ports :
2026-03-01 20:34:49 +00:00
- "5433:5432"
2026-03-01 03:10:35 +00:00
volumes :
- pg_data:/var/lib/postgresql/data
2026-03-01 05:59:27 +00:00
- ./docker-init-db.sh:/docker-entrypoint-initdb.d/01-init-db.sh:ro
2026-03-01 06:11:20 +00:00
- ./01-llm-cost-router/migrations:/migrations/01-route:ro
- ./02-iac-drift-detection/saas/migrations:/migrations/02-drift:ro
2026-03-01 05:59:27 +00:00
- ./03-alert-intelligence/migrations:/migrations/03-alert:ro
- ./04-lightweight-idp/migrations:/migrations/04-portal:ro
- ./05-aws-cost-anomaly/migrations:/migrations/05-cost:ro
- ./06-runbook-automation/saas/migrations:/migrations/06-run:ro
2026-03-01 03:10:35 +00:00
healthcheck :
test : [ "CMD-SHELL" , "pg_isready -U dd0c" ]
interval : 5s
timeout : 3s
retries : 5
redis :
image : redis:7-alpine
ports :
- "6379:6379"
healthcheck :
test : [ "CMD" , "redis-cli" , "ping" ]
interval : 5s
timeout : 3s
retries : 5
meilisearch :
image : getmeili/meilisearch:v1.8
environment :
MEILI_ENV : development
ports :
- "7700:7700"
volumes :
- meili_data:/meili_data
2026-03-02 05:15:37 +00:00
2026-03-03 05:04:42 +00:00
# --- DB User Bootstrap (idempotent, runs every up) ---
db-setup :
image : postgres:16-alpine
restart : "no"
depends_on :
postgres : { condition : service_healthy }
environment :
PGHOST : postgres
PGUSER : ${POSTGRES_USER:-dd0c}
PGPASSWORD : ${POSTGRES_PASSWORD:-dd0c-dev}
DB_DRIFT_PASSWORD : ${DB_DRIFT_PASSWORD:-dd0c-dev}
DB_ALERT_PASSWORD : ${DB_ALERT_PASSWORD:-dd0c-dev}
DB_PORTAL_PASSWORD : ${DB_PORTAL_PASSWORD:-dd0c-dev}
DB_COST_PASSWORD : ${DB_COST_PASSWORD:-dd0c-dev}
DB_RUN_PASSWORD : ${DB_RUN_PASSWORD:-dd0c-dev}
entrypoint : [ "sh" , "-c" ]
command :
- |
set -e
create_user() {
local db=$$1 user=$$2 pass=$$3
psql -d postgres -c "DO \$$\$$ BEGIN CREATE USER $$user WITH PASSWORD '$$pass'; EXCEPTION WHEN duplicate_object THEN ALTER USER $$user WITH PASSWORD '$$pass'; END \$$\$$;"
psql -d "$$db" -c "GRANT CONNECT ON DATABASE $$db TO $$user;"
psql -d "$$db" -c "GRANT USAGE ON SCHEMA public TO $$user;"
psql -d "$$db" -c "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO $$user;"
psql -d "$$db" -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $$user;"
psql -d "$$db" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO $$user;"
psql -d "$$db" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO $$user;"
echo "✓ $$user → $$db"
}
create_user dd0c_drift dd0c_drift "$$DB_DRIFT_PASSWORD"
create_user dd0c_alert dd0c_alert "$$DB_ALERT_PASSWORD"
create_user dd0c_portal dd0c_portal "$$DB_PORTAL_PASSWORD"
create_user dd0c_cost dd0c_cost "$$DB_COST_PASSWORD"
create_user dd0c_run dd0c_run "$$DB_RUN_PASSWORD"
echo "db-setup complete"
2026-03-01 03:10:35 +00:00
# --- dd0c Products ---
2026-03-01 06:11:20 +00:00
# P1: LLM Cost Router (Rust — API server)
2026-03-01 19:15:49 +00:00
# NOTE: Rust services are behind the "rust" profile because they take 10+ min to compile.
# Start without Rust: docker compose up -d
# Start with Rust: docker compose --profile rust up -d
2026-03-01 06:11:20 +00:00
route-api :
2026-03-01 19:15:49 +00:00
profiles : [ "rust" ]
2026-03-01 06:11:20 +00:00
build :
context : ./01-llm-cost-router
dockerfile : Dockerfile
command : [ "dd0c-api" ]
ports :
- "3001:3000"
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 06:11:20 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c:${POSTGRES_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_route
2026-03-01 06:11:20 +00:00
REDIS_URL : redis://redis:6379
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 06:11:20 +00:00
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
# P1: LLM Cost Router (Rust — proxy)
route-proxy :
2026-03-01 19:15:49 +00:00
profiles : [ "rust" ]
2026-03-01 06:11:20 +00:00
build :
context : ./01-llm-cost-router
dockerfile : Dockerfile
command : [ "dd0c-proxy" ]
ports :
- "8080:8080"
environment :
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c:${POSTGRES_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_route
2026-03-01 06:11:20 +00:00
REDIS_URL : redis://redis:6379
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
# P1: LLM Cost Router (Rust — worker)
route-worker :
2026-03-01 19:15:49 +00:00
profiles : [ "rust" ]
2026-03-01 06:11:20 +00:00
build :
context : ./01-llm-cost-router
dockerfile : Dockerfile
command : [ "dd0c-worker" ]
environment :
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c:${POSTGRES_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_route
2026-03-01 06:11:20 +00:00
REDIS_URL : redis://redis:6379
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
# P2: IaC Drift Detection (SaaS)
drift :
2026-03-02 13:31:11 +00:00
image : reg.dd0c.net/dd0c-drift:latest
2026-03-02 05:21:33 +00:00
build :
context : ./02-iac-drift-detection/saas
dockerfile : Dockerfile
2026-03-01 06:11:20 +00:00
ports :
- "3002:3000"
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 06:11:20 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c_drift:${DB_DRIFT_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_drift
2026-03-01 06:11:20 +00:00
REDIS_URL : redis://redis:6379
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 06:11:20 +00:00
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
2026-03-03 05:04:42 +00:00
db-setup : { condition : service_completed_successfully }
2026-03-01 06:11:20 +00:00
2026-03-01 03:10:35 +00:00
# P3: Alert Intelligence
alert :
2026-03-02 13:31:11 +00:00
image : reg.dd0c.net/dd0c-alert:latest
2026-03-02 05:21:33 +00:00
build :
context : ./03-alert-intelligence
dockerfile : Dockerfile
2026-03-01 03:10:35 +00:00
ports :
- "3003:3000"
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 03:10:35 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c_alert:${DB_ALERT_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_alert
2026-03-01 03:10:35 +00:00
REDIS_URL : redis://redis:6379
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 03:10:35 +00:00
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
2026-03-03 05:04:42 +00:00
db-setup : { condition : service_completed_successfully }
2026-03-01 03:10:35 +00:00
# P4: Lightweight IDP / Service Catalog
portal :
2026-03-02 13:31:11 +00:00
image : reg.dd0c.net/dd0c-portal:latest
2026-03-02 05:21:33 +00:00
build :
context : ./04-lightweight-idp
dockerfile : Dockerfile
2026-03-01 03:10:35 +00:00
ports :
- "3004:3000"
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 03:10:35 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c_portal:${DB_PORTAL_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_portal
2026-03-01 03:10:35 +00:00
REDIS_URL : redis://redis:6379
MEILI_URL : http://meilisearch:7700
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 03:10:35 +00:00
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
meilisearch : { condition : service_started }
2026-03-03 05:04:42 +00:00
db-setup : { condition : service_completed_successfully }
2026-03-01 03:10:35 +00:00
# P5: AWS Cost Anomaly Detection
cost :
2026-03-02 13:31:11 +00:00
image : reg.dd0c.net/dd0c-cost:latest
2026-03-02 05:21:33 +00:00
build :
context : ./05-aws-cost-anomaly
dockerfile : Dockerfile
2026-03-01 03:10:35 +00:00
ports :
2026-03-01 19:07:52 +00:00
- "3007:3000"
2026-03-01 03:10:35 +00:00
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 03:10:35 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c_cost:${DB_COST_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_cost
2026-03-01 03:10:35 +00:00
REDIS_URL : redis://redis:6379
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 03:10:35 +00:00
ANOMALY_THRESHOLD : "50"
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
2026-03-03 05:04:42 +00:00
db-setup : { condition : service_completed_successfully }
2026-03-01 03:10:35 +00:00
# P6: Runbook Automation (SaaS)
run :
2026-03-02 13:31:11 +00:00
image : reg.dd0c.net/dd0c-run:latest
2026-03-02 05:21:33 +00:00
build :
context : ./06-runbook-automation/saas
dockerfile : Dockerfile
2026-03-01 03:10:35 +00:00
ports :
- "3006:3000"
environment :
2026-03-01 20:41:47 +00:00
NODE_ENV : production
2026-03-01 03:10:35 +00:00
PORT : "3000"
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
DATABASE_URL : postgresql://dd0c_run:${DB_RUN_PASSWORD:-dd0c-dev}@postgres:5432/dd0c_run
2026-03-01 03:10:35 +00:00
REDIS_URL : redis://redis:6379
Security hardening: auth encapsulation, pool restriction, rate limiting, invites, async webhooks
Phase 1 (Security Critical):
- Auth plugin encapsulation: replaced global addHook with Fastify plugin scope
- Removed startsWith URL matching; public routes registered outside auth scope
- JWT verify now enforces algorithms: ['HS256'] (prevents algorithm confusion)
- Raw pool no longer exported from db.ts; systemQuery() + getPoolForAuth() instead
- withTenant() remains primary tenant-scoped query path
Phase 2 (Infrastructure):
- docker-compose.yml: all secrets via env var substitution (${VAR:-default})
- Per-service Postgres users (dd0c_drift, dd0c_alert, etc.) in docker-init-db.sh
- .env.example with all configurable secrets
- build-push.sh uses $REGISTRY_PASSWORD instead of hardcoded
- .gitignore excludes .env files
- @fastify/rate-limit: 100 req/min global, 5/min login, 3/min signup
- CORS_ORIGIN default changed from '*' to 'http://localhost:5173'
Phase 3 (Product):
- Team invite flow: tenant_invites table, POST /invite, GET /invites, DELETE /invites/:id
- Signup accepts optional invite_token to join existing tenant
- Async webhook ingestion (P3): LPUSH to Redis, BRPOP worker, dead-letter queue
Console:
- All 5 product modules wired: drift, alert, portal, cost, run
- PageHeader accepts children prop
- 71 modules, 70KB gzipped production build
All 6 projects compile clean (tsc --noEmit).
2026-03-02 23:53:55 +00:00
JWT_SECRET : ${JWT_SECRET:-dev-secret-change-me-in-production!!}
2026-03-01 03:10:35 +00:00
LOG_LEVEL : info
depends_on :
postgres : { condition : service_healthy }
redis : { condition : service_healthy }
2026-03-03 05:04:42 +00:00
db-setup : { condition : service_completed_successfully }
2026-03-01 03:10:35 +00:00
2026-03-03 00:36:48 +00:00
# dd0c Console (React SPA)
console :
image : reg.dd0c.net/dd0c-console:latest
build :
context : ./console
dockerfile : Dockerfile
ports :
- "3010:80"
# dd0c Marketing Site (Astro)
marketing :
image : reg.dd0c.net/dd0c-marketing:latest
build :
context : ./marketing/site
dockerfile : Dockerfile
ports :
- "3011:80"
2026-03-01 03:10:35 +00:00
volumes :
pg_data :
meili_data :