# Banatie Production - VPS Isolated Deployment # ============================================ # This is the production docker-compose file used on VPS at /opt/banatie/ # Last Updated: December 23, 2025 # # Usage: # docker compose --env-file .env --env-file secrets.env up -d # docker compose --env-file .env --env-file secrets.env build --no-cache # # Key differences from dev: # - Uses external proxy-network for Caddy integration # - All services isolated in banatie-internal network # - MinIO with 4 drives for full S3 compatibility # - Secrets stored in separate secrets.env file services: # ---------------------------------------- # API Service - Express.js REST API # ---------------------------------------- banatie-api: build: context: /home/usul/workspace/projects/banatie-service dockerfile: apps/api-service/Dockerfile target: production container_name: banatie-api restart: unless-stopped networks: - banatie-internal - proxy-network depends_on: banatie-postgres: condition: service_healthy banatie-minio: condition: service_healthy env_file: - .env - secrets.env environment: - IS_DOCKER=true - NODE_ENV=production volumes: - ./logs/api:/app/apps/api-service/logs - ./data/api-results:/app/results - ./data/api-uploads:/app/uploads healthcheck: # Note: Alpine images don't have curl by default # Using wget instead, but may still show "unhealthy" - service works correctly test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # ---------------------------------------- # Landing Page - Next.js 15.5.9 # ---------------------------------------- banatie-landing: build: context: /home/usul/workspace/projects/banatie-service dockerfile: apps/landing/Dockerfile container_name: banatie-landing restart: unless-stopped networks: - banatie-internal - proxy-network depends_on: - banatie-postgres env_file: - .env - secrets.env environment: - IS_DOCKER=true - NODE_ENV=production - HOSTNAME=0.0.0.0 - WAITLIST_LOGS_PATH=/app/waitlist-logs volumes: - ./data/waitlist-logs:/app/waitlist-logs healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000"] interval: 30s timeout: 10s retries: 3 start_period: 30s # ---------------------------------------- # PostgreSQL Database # ---------------------------------------- banatie-postgres: image: postgres:15-alpine container_name: banatie-postgres restart: unless-stopped networks: - banatie-internal volumes: - ./data/postgres:/var/lib/postgresql/data - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 30s timeout: 10s retries: 3 start_period: 40s # ---------------------------------------- # MinIO Object Storage (S3-compatible) # ---------------------------------------- banatie-minio: image: quay.io/minio/minio:latest container_name: banatie-minio restart: unless-stopped networks: - banatie-internal - proxy-network volumes: # 4 drives for SNMD mode (full S3 compatibility) - ./data/minio/drive1:/data1 - ./data/minio/drive2:/data2 - ./data/minio/drive3:/data3 - ./data/minio/drive4:/data4 environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} MINIO_BROWSER_REDIRECT_URL: https://storage.banatie.app MINIO_SERVER_URL: https://cdn.banatie.app command: server /data{1...4} --console-address ":9001" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 10s retries: 3 start_period: 40s # ---------------------------------------- # Storage Initialization (runs once) # ---------------------------------------- banatie-storage-init: image: minio/mc:latest container_name: banatie-storage-init networks: - banatie-internal depends_on: banatie-minio: condition: service_healthy env_file: - secrets.env entrypoint: - /bin/sh - -c - | echo '=== MinIO Storage Initialization ===' mc alias set storage http://banatie-minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD} mc mb --ignore-existing storage/banatie mc admin user add storage $${MINIO_ACCESS_KEY} $${MINIO_SECRET_KEY} || echo 'User may already exist' mc admin policy attach storage readwrite --user=$${MINIO_ACCESS_KEY} || echo 'Policy may already be attached' cat > /tmp/lifecycle.json <<'LCEOF' {"Rules":[{"ID":"temp-cleanup","Status":"Enabled","Filter":{"Prefix":"temp/"},"Expiration":{"Days":7}}]} LCEOF mc ilm import storage/banatie < /tmp/lifecycle.json || echo 'Lifecycle policy may already exist' echo '=== Storage Initialization Completed ===' exit 0 restart: "no" # ---------------------------------------- # Networks # ---------------------------------------- networks: # Internal network for service communication # internal: true means no outbound access banatie-internal: driver: bridge internal: true # External network shared with Caddy reverse proxy # Must be created by Caddy's docker-compose first proxy-network: name: services_proxy-network external: true