version: '3.8' services: # PostgreSQL Database postgres: image: postgres:15-alpine container_name: uploader_postgres restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-uploader_bot} POSTGRES_USER: ${POSTGRES_USER:-uploader} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password} POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" volumes: - postgres_data:/var/lib/postgresql/data - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro ports: - "${POSTGRES_PORT:-5432}:5432" networks: - uploader_network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-uploader} -d ${POSTGRES_DB:-uploader_bot}"] interval: 10s timeout: 5s retries: 5 start_period: 30s # Redis Cache redis: image: redis:7-alpine container_name: uploader_redis restart: unless-stopped command: > redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru --save 900 1 --save 300 10 --save 60 10000 volumes: - redis_data:/data - ./config/redis.conf:/usr/local/etc/redis/redis.conf:ro ports: - "${REDIS_PORT:-6379}:6379" networks: - uploader_network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s # Main Application app: build: context: . dockerfile: Dockerfile.new target: production container_name: uploader_app restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: # Database DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-uploader}:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/${POSTGRES_DB:-uploader_bot} # Redis REDIS_URL: redis://redis:6379/0 # Application PROJECT_HOST: ${PROJECT_HOST:-http://localhost:15100} SANIC_PORT: 15100 DEBUG: ${DEBUG:-false} LOG_LEVEL: ${LOG_LEVEL:-INFO} # Telegram TELEGRAM_API_KEY: ${TELEGRAM_API_KEY} CLIENT_TELEGRAM_API_KEY: ${CLIENT_TELEGRAM_API_KEY} # TON Blockchain TESTNET: ${TESTNET:-false} TONCENTER_HOST: ${TONCENTER_HOST:-https://toncenter.com/api/v2/} TONCENTER_API_KEY: ${TONCENTER_API_KEY} # Security SECRET_KEY: ${SECRET_KEY} JWT_SECRET_KEY: ${JWT_SECRET_KEY} # File Storage UPLOADS_DIR: /app/data # Services INDEXER_ENABLED: ${INDEXER_ENABLED:-true} TON_DAEMON_ENABLED: ${TON_DAEMON_ENABLED:-true} LICENSE_SERVICE_ENABLED: ${LICENSE_SERVICE_ENABLED:-true} CONVERT_SERVICE_ENABLED: ${CONVERT_SERVICE_ENABLED:-true} # Monitoring METRICS_ENABLED: ${METRICS_ENABLED:-true} HEALTH_CHECK_ENABLED: ${HEALTH_CHECK_ENABLED:-true} # Rate Limiting RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true} RATE_LIMIT_REQUESTS: ${RATE_LIMIT_REQUESTS:-100} RATE_LIMIT_WINDOW: ${RATE_LIMIT_WINDOW:-60} volumes: - app_data:/app/data - app_logs:/app/logs - ./config:/app/config:ro ports: - "${SANIC_PORT:-15100}:15100" - "${METRICS_PORT:-9090}:9090" networks: - uploader_network labels: - "traefik.enable=true" - "traefik.http.routers.uploader.rule=Host(`${DOMAIN:-localhost}`)" - "traefik.http.routers.uploader.entrypoints=web,websecure" - "traefik.http.routers.uploader.tls.certresolver=letsencrypt" - "traefik.http.services.uploader.loadbalancer.server.port=15100" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:15100/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # Background Services (Alternative architecture - separate containers) indexer: build: context: . dockerfile: Dockerfile.new target: production container_name: uploader_indexer restart: unless-stopped command: python -m app.services.indexer depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-uploader}:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/${POSTGRES_DB:-uploader_bot} REDIS_URL: redis://redis:6379/0 TELEGRAM_API_KEY: ${TELEGRAM_API_KEY} CLIENT_TELEGRAM_API_KEY: ${CLIENT_TELEGRAM_API_KEY} TESTNET: ${TESTNET:-false} TONCENTER_HOST: ${TONCENTER_HOST:-https://toncenter.com/api/v2/} TONCENTER_API_KEY: ${TONCENTER_API_KEY} LOG_LEVEL: ${LOG_LEVEL:-INFO} SERVICE_NAME: indexer volumes: - app_logs:/app/logs networks: - uploader_network profiles: - separate-services ton_daemon: build: context: . dockerfile: Dockerfile.new target: production container_name: uploader_ton_daemon restart: unless-stopped command: python -m app.services.ton_daemon depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-uploader}:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/${POSTGRES_DB:-uploader_bot} REDIS_URL: redis://redis:6379/0 TESTNET: ${TESTNET:-false} TONCENTER_HOST: ${TONCENTER_HOST:-https://toncenter.com/api/v2/} TONCENTER_API_KEY: ${TONCENTER_API_KEY} LOG_LEVEL: ${LOG_LEVEL:-INFO} SERVICE_NAME: ton_daemon volumes: - app_logs:/app/logs networks: - uploader_network profiles: - separate-services # Monitoring and Observability prometheus: image: prom/prometheus:latest container_name: uploader_prometheus restart: unless-stopped 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=200h' - '--web.enable-lifecycle' volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus ports: - "9091:9090" networks: - uploader_network profiles: - monitoring grafana: image: grafana/grafana:latest container_name: uploader_grafana restart: unless-stopped environment: GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin} GF_USERS_ALLOW_SIGN_UP: false volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro ports: - "3001:3000" networks: - uploader_network profiles: - monitoring # Reverse Proxy (optional) traefik: image: traefik:v3.0 container_name: uploader_traefik restart: unless-stopped command: - '--api.dashboard=true' - '--api.insecure=true' - '--providers.docker=true' - '--providers.docker.exposedbydefault=false' - '--entrypoints.web.address=:80' - '--entrypoints.websecure.address=:443' - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true' - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web' - '--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL:-admin@example.com}' - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json' ports: - "80:80" - "443:443" - "8080:8080" # Traefik dashboard volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_data:/letsencrypt networks: - uploader_network profiles: - proxy # Database backup service postgres_backup: image: postgres:15-alpine container_name: uploader_backup restart: "no" depends_on: - postgres environment: POSTGRES_DB: ${POSTGRES_DB:-uploader_bot} POSTGRES_USER: ${POSTGRES_USER:-uploader} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password} BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 2 * * *} # Daily at 2 AM volumes: - backup_data:/backups - ./scripts/backup.sh:/backup.sh:ro command: /backup.sh networks: - uploader_network profiles: - backup # Named volumes for data persistence volumes: postgres_data: driver: local redis_data: driver: local app_data: driver: local app_logs: driver: local prometheus_data: driver: local grafana_data: driver: local traefik_data: driver: local backup_data: driver: local # Custom network networks: uploader_network: driver: bridge ipam: config: - subnet: 172.20.0.0/16