banatie-service/infrastructure/docker-compose.production.yml

182 lines
5.7 KiB
YAML

# 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