version: "3.8" services: # API Service janua-api: image: janua/api:latest container_name: janua-api restart: unless-stopped environment: - ENVIRONMENT=production - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} - JWT_SECRET_KEY=${JWT_SECRET_KEY} - SECRET_KEY=${SECRET_KEY} - SENDGRID_API_KEY=${SENDGRID_API_KEY} - SENTRY_DSN=${SENTRY_DSN} # Database SSL Configuration - DATABASE_SSL_MODE=${DATABASE_SSL_MODE:-require} - DATABASE_SSL_CA_FILE=/etc/ssl/certs/db-ca.crt # Email (Resend) - Centralized for all MADFAM apps - RESEND_API_KEY=${RESEND_API_KEY} # Internal API - Service-to-service communication for MADFAM apps - INTERNAL_API_KEY=${INTERNAL_API_KEY} volumes: # Mount database CA certificate for SSL verification - ./ssl/db/ca.crt:/etc/ssl/certs/db-ca.crt:ro ports: - "4100:4100" # MADFAM standard: 4100 = Janua API healthcheck: test: ["CMD", "curl", "-f", "http://localhost:4100/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - janua-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: "2.0" memory: 2G reservations: cpus: "1.0" memory: 1G # Database with SSL enabled postgres: image: postgres:15-alpine container_name: janua-postgres restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-janua} POSTGRES_USER: ${POSTGRES_USER:-janua} POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in an untracked local env file}" POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./postgresql.conf:/etc/postgresql/postgresql.conf - ./pg_hba.conf:/etc/postgresql/pg_hba.conf # SSL certificates for encrypted connections - ./ssl/db/server.crt:/var/lib/postgresql/server.crt:ro - ./ssl/db/server.key:/var/lib/postgresql/server.key:ro - ./ssl/db/ca.crt:/var/lib/postgresql/ca.crt:ro command: > postgres -c config_file=/etc/postgresql/postgresql.conf -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c ssl_ca_file=/var/lib/postgresql/ca.crt healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-janua} -d ${POSTGRES_DB:-janua}", ] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: - janua-network deploy: resources: limits: cpus: "1.5" memory: 1.5G reservations: cpus: "0.5" memory: 512M # Redis Cache redis: image: redis:7-alpine container_name: janua-redis restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru ports: - "6379:6379" volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 30s timeout: 3s retries: 3 start_period: 30s networks: - janua-network deploy: resources: limits: cpus: "0.5" memory: 512M reservations: cpus: "0.25" memory: 256M # Nginx Load Balancer nginx: image: nginx:alpine container_name: janua-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx-ssl.conf:/etc/nginx/conf.d/default.conf - ./ssl/certs:/etc/ssl/certs - ./ssl/private:/etc/ssl/private - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - janua-api - janua-dashboard - janua-website - janua-docs networks: - janua-network healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health", ] interval: 30s timeout: 10s retries: 3 deploy: resources: limits: cpus: "0.5" memory: 256M # Dashboard Frontend (app.janua.dev) janua-dashboard: image: janua/dashboard:latest container_name: janua-dashboard restart: unless-stopped environment: - NODE_ENV=production # INTERNAL_API_URL: Server-side API route calls (runtime, internal network) # Uses Docker network hostname for container-to-container communication - INTERNAL_API_URL=http://janua-api:4100 # NEXT_PUBLIC_API_URL: Client-side calls (build-time, external URL) - NEXT_PUBLIC_API_URL=https://api.janua.dev - NEXT_PUBLIC_APP_URL=https://app.janua.dev ports: - "4101:3000" # MADFAM standard: 4101 = Janua Dashboard healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health", ] interval: 30s timeout: 10s retries: 3 networks: - janua-network # Public Website (janua.dev) janua-website: image: janua/website:latest container_name: janua-website restart: unless-stopped environment: - NODE_ENV=production - NEXT_PUBLIC_API_URL=https://api.janua.dev - NEXT_PUBLIC_APP_URL=https://app.janua.dev ports: - "4104:3000" # MADFAM standard: 4104 = Janua Website healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health", ] interval: 30s timeout: 10s retries: 3 networks: - janua-network # Documentation Site (docs.janua.dev) janua-docs: image: janua/docs:latest container_name: janua-docs restart: unless-stopped environment: - NODE_ENV=production ports: - "4103:3000" # MADFAM standard: 4103 = Janua Docs healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health", ] interval: 30s timeout: 10s retries: 3 networks: - janua-network # Monitoring Stack prometheus: image: prom/prometheus:v2.45.0 container_name: janua-prometheus restart: unless-stopped ports: - "9090:9090" volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml - ./monitoring/rules:/etc/prometheus/rules - prometheus_data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--web.console.libraries=/etc/prometheus/console_libraries" - "--web.console.templates=/etc/prometheus/consoles" - "--storage.tsdb.retention.time=30d" - "--web.enable-lifecycle" networks: - janua-network healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/healthy", ] interval: 30s timeout: 10s retries: 3 grafana: image: grafana/grafana:10.0.0 container_name: janua-grafana restart: unless-stopped ports: - "3003:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} - GF_USERS_ALLOW_SIGN_UP=false - GF_INSTALL_PLUGINS=grafana-piechart-panel volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana-dashboard.json:/etc/grafana/provisioning/dashboards/janua.json networks: - janua-network healthcheck: test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1", ] interval: 30s timeout: 10s retries: 3 # PostgreSQL Exporter for Prometheus postgres-exporter: image: prometheuscommunity/postgres-exporter container_name: janua-postgres-exporter restart: unless-stopped ports: - "9187:9187" environment: # SSL enabled: verify-full requires CA cert, require just needs encryption DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER:-janua}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-janua}?sslmode=require" volumes: # Mount CA certificate for SSL verification (optional, for sslmode=verify-full) - ./ssl/db/ca.crt:/etc/ssl/certs/ca.crt:ro depends_on: - postgres networks: - janua-network # Redis Exporter for Prometheus redis-exporter: image: oliver006/redis_exporter container_name: janua-redis-exporter restart: unless-stopped ports: - "9121:9121" environment: - REDIS_ADDR=redis://redis:6379 - REDIS_PASSWORD=${REDIS_PASSWORD} depends_on: - redis networks: - janua-network # Node Exporter for System Metrics node-exporter: image: prom/node-exporter:v1.6.0 container_name: janua-node-exporter restart: unless-stopped ports: - "9100:9100" volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - "--path.procfs=/host/proc" - "--path.rootfs=/rootfs" - "--path.sysfs=/host/sys" - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)" networks: - janua-network # ============================================ # Automated PostgreSQL Backup Service # ============================================ # Runs daily backups at 2 AM UTC with 30-day retention # Backups are stored locally and can be synced to S3/GCS postgres-backup: image: prodrigestivill/postgres-backup-local:15 container_name: janua-postgres-backup restart: unless-stopped environment: - POSTGRES_HOST=postgres - POSTGRES_DB=${POSTGRES_DB:-janua} - POSTGRES_USER=${POSTGRES_USER:-janua} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_EXTRA_OPTS=--blobs --format=custom --compress=9 # Schedule: Daily at 2:00 AM UTC - SCHEDULE=0 2 * * * # Keep 30 days of daily backups - BACKUP_KEEP_DAYS=30 # Keep 4 weekly backups (Sundays) - BACKUP_KEEP_WEEKS=4 # Keep 6 monthly backups (1st of month) - BACKUP_KEEP_MONTHS=6 # Naming convention - BACKUP_SUFFIX=.sql.gz # Health check interval - HEALTHCHECK_PORT=8080 # Optional: SSL mode for backup connection - POSTGRES_SSL_MODE=require volumes: - postgres_backups:/backups # Mount CA certificate for SSL verification - ./ssl/db/ca.crt:/etc/ssl/certs/ca.crt:ro depends_on: postgres: condition: service_healthy networks: - janua-network healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"] interval: 60s timeout: 10s retries: 3 deploy: resources: limits: cpus: "0.5" memory: 512M reservations: cpus: "0.1" memory: 128M labels: - "com.janua.description=Automated PostgreSQL backup service" - "com.janua.backup.schedule=Daily at 2:00 AM UTC" - "com.janua.backup.retention=30 days daily, 4 weekly, 6 monthly" networks: janua-network: driver: bridge ipam: driver: default config: - subnet: 172.20.0.0/16 volumes: postgres_data: driver: local postgres_backups: driver: local driver_opts: type: none o: bind device: /var/backups/janua/postgres redis_data: driver: local prometheus_data: driver: local grafana_data: driver: local