version: '3.8' services: postgres: image: postgres:16-alpine container_name: stripemeter-postgres restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-stripemeter} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME:-stripemeter} POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" ports: - "${DB_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./infra/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-stripemeter}"] interval: 10s timeout: 5s retries: 5 networks: - stripemeter-network redis: image: redis:7-alpine container_name: stripemeter-redis restart: unless-stopped ports: - "${REDIS_PORT:-6379}:6379" volumes: - redis_data:/data command: > redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - stripemeter-network api: build: context: . dockerfile: Dockerfile container_name: stripemeter-api restart: unless-stopped ports: - "${API_PORT:-3000}:3000" environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-stripemeter}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-stripemeter} REDIS_URL: redis://redis:6379 STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} JWT_SECRET: ${JWT_SECRET} ENCRYPTION_KEY: ${ENCRYPTION_KEY} LOG_LEVEL: ${LOG_LEVEL:-info} depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health/ready', (r) => {r.statusCode === 200 ? process.exit(0) : process.exit(1)})"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - stripemeter-network workers: build: context: . dockerfile: Dockerfile container_name: stripemeter-workers restart: unless-stopped command: ["node", "apps/workers/dist/index.js"] environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-stripemeter}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-stripemeter} REDIS_URL: redis://redis:6379 STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-5} AGGREGATION_INTERVAL_MS: ${AGGREGATION_INTERVAL_MS:-5000} STRIPE_WRITER_INTERVAL_MS: ${STRIPE_WRITER_INTERVAL_MS:-10000} RECONCILIATION_INTERVAL_MS: ${RECONCILIATION_INTERVAL_MS:-3600000} LOG_LEVEL: ${LOG_LEVEL:-info} depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - stripemeter-network # Optional: Prometheus for metrics prometheus: image: prom/prometheus:latest container_name: stripemeter-prometheus restart: unless-stopped ports: - "${PROMETHEUS_PORT:-9090}:9090" volumes: - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/usr/share/prometheus/console_libraries' - '--web.console.templates=/usr/share/prometheus/consoles' networks: - stripemeter-network profiles: - monitoring # Optional: Grafana for visualization grafana: image: grafana/grafana:latest container_name: stripemeter-grafana restart: unless-stopped ports: - "${GRAFANA_PORT:-3001}:3000" environment: GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin} GF_INSTALL_PLUGINS: redis-datasource volumes: - grafana_data:/var/lib/grafana - ./infra/grafana/provisioning:/etc/grafana/provisioning:ro depends_on: - prometheus networks: - stripemeter-network profiles: - monitoring # Optional: Nginx reverse proxy nginx: image: nginx:alpine container_name: stripemeter-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./infra/nginx/ssl:/etc/nginx/ssl:ro depends_on: - api networks: - stripemeter-network profiles: - production networks: stripemeter-network: driver: bridge volumes: postgres_data: redis_data: prometheus_data: grafana_data: