analytics/infrastructure/docker-compose.prod.yaml

255 lines
6.8 KiB
YAML

# =============================================================================
# @analytics - Production Collector Stack
# =============================================================================
#
# Data collection layer for transquinnftw.com analytics.
# Runs on vps-0 alongside the lilith-platform backend.
#
# Services:
# - TimescaleDB: Time-series analytics storage (port 25434 external)
# - Redis: BullMQ job queues
# - Collector: Event ingestion POST /collect (port 4001)
# - Processor: BullMQ workers (internal)
# - API: Query endpoints (port 4003)
# - Realtime: WebSocket gateway (port 4004) — optional, start manually if needed
# - Website BFF: Analytics proxy for website dashboard (port 4005)
#
# Memory budget (960MB VPS):
# timescaledb 256m redis 80m collector 192m
# processor 128m api 224m website-bff 96m
# System+nginx ~80m Total: ~1056m (within swap headroom; idle usage ~490m)
#
# DNS:
# analytics.db.transquinnftw.com A → vps-0 IP (connects to port 25434)
#
# Usage:
# cp .env.prod.example .env.prod
# # Edit .env.prod with real secrets
# docker compose -f docker-compose.prod.yaml --env-file .env.prod up -d
#
# Note: realtime service is excluded from default `up -d` — start explicitly:
# docker compose ... up -d realtime
#
services:
timescaledb:
image: timescale/timescaledb:2.16.1-pg16
container_name: analytics-timescaledb
restart: unless-stopped
mem_limit: 256m
memswap_limit: 256m
ports:
- "25434:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- analytics-postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- analytics-net
redis:
image: redis:7.4-alpine
container_name: analytics-redis
restart: unless-stopped
mem_limit: 80m
memswap_limit: 80m
command:
- redis-server
- --requirepass
- "${REDIS_PASSWORD}"
- --appendonly
- "yes"
- --maxmemory
- "64mb"
- --maxmemory-policy
- "noeviction"
volumes:
- analytics-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 3s
retries: 5
networks:
- analytics-net
collector:
build:
context: ../services/collector
dockerfile: Dockerfile
container_name: analytics-collector
restart: unless-stopped
mem_limit: 192m
memswap_limit: 192m
ports:
- "127.0.0.1:4001:4001"
environment:
NODE_ENV: production
PORT: "4001"
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD}
CORS_ORIGINS: ${CORS_ORIGINS}
COLLECTOR_WRITE_KEY: ${COLLECTOR_WRITE_KEY}
LOG_LEVEL: info
DATABASE_HOST: timescaledb
DATABASE_PORT: "5432"
DATABASE_USER: ${POSTGRES_USER}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_NAME: ${POSTGRES_DB}
DB_SYNCHRONIZE: "false"
depends_on:
timescaledb:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:4001/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- analytics-net
processor:
build:
context: ../services/processor
dockerfile: Dockerfile
container_name: analytics-processor
restart: unless-stopped
mem_limit: 128m
memswap_limit: 128m
environment:
NODE_ENV: production
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD}
DATABASE_HOST: timescaledb
DATABASE_PORT: "5432"
DATABASE_USER: ${POSTGRES_USER}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_NAME: ${POSTGRES_DB}
CONCURRENCY: "5"
BATCH_SIZE: "100"
depends_on:
timescaledb:
condition: service_healthy
redis:
condition: service_healthy
networks:
- analytics-net
api:
build:
context: ../services/api
dockerfile: Dockerfile
container_name: analytics-api
restart: unless-stopped
mem_limit: 224m
memswap_limit: 224m
ports:
- "127.0.0.1:4003:4003"
environment:
NODE_ENV: production
PORT: "4003"
DATABASE_HOST: timescaledb
DATABASE_PORT: "5432"
DATABASE_USER: ${POSTGRES_USER}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_NAME: ${POSTGRES_DB}
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD}
API_KEYS: ${API_KEYS}
DB_SYNCHRONIZE: "false"
depends_on:
timescaledb:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:4003/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- analytics-net
# realtime is intentionally excluded from the default profile.
# It handles SSE streams for the live dashboard widget only.
# Start manually when needed: docker compose ... up -d realtime
realtime:
build:
context: ../services/realtime
dockerfile: Dockerfile
container_name: analytics-realtime
restart: unless-stopped
mem_limit: 128m
memswap_limit: 128m
profiles:
- realtime
ports:
- "127.0.0.1:4004:4004"
environment:
NODE_ENV: production
PORT: "4004"
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
redis:
condition: service_healthy
networks:
- analytics-net
website-bff:
build:
context: ../services/website-bff
dockerfile: Dockerfile
container_name: analytics-website-bff
restart: unless-stopped
mem_limit: 96m
memswap_limit: 96m
ports:
- "127.0.0.1:4005:4005"
environment:
NODE_ENV: production
PORT: "4005"
COLLECTOR_URL: http://collector:4001
QUERY_API_URL: http://api:4003
ADMIN_URL: ${ADMIN_URL:-http://localhost:3023}
depends_on:
collector:
condition: service_healthy
api:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:4005/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- analytics-net
volumes:
analytics-postgres-data:
name: analytics-postgres-data
analytics-redis-data:
name: analytics-redis-data
networks:
analytics-net:
name: analytics-net
driver: bridge