version: '3.8' services: # Application Services backend: image: ghcr.io/${GITHUB_REPOSITORY:-malaysian-sme-platform}:latest-backend restart: unless-stopped environment: - DEBUG=False - ENVIRONMENT=production - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} - REDIS_URL=redis://redis:6379/0 - SECRET_KEY=${SECRET_KEY} - ALLOWED_HOSTS=${ALLOWED_HOSTS} - CSRF_TRUSTED_ORIGINS=${CSRF_TRUSTED_ORIGINS} - SENTRY_DSN=${SENTRY_DSN} - ROLLBAR_ACCESS_TOKEN=${ROLLBAR_ACCESS_TOKEN} volumes: - backend_logs:/app/logs - media_files:/app/media depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health/"] interval: 30s timeout: 10s retries: 3 deploy: replicas: 3 resources: limits: memory: 1G cpus: '0.5' reservations: memory: 512M cpus: '0.25' frontend: image: ghcr.io/${GITHUB_REPOSITORY:-malaysian-sme-platform}:latest-frontend restart: unless-stopped environment: - REACT_APP_API_URL=https://api.malaysian-sme-platform.com - REACT_APP_ENVIRONMENT=production - REACT_APP_SENTRY_DSN=${FRONTEND_SENTRY_DSN} depends_on: - backend healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 30s timeout: 10s retries: 3 deploy: replicas: 2 resources: limits: memory: 512M cpus: '0.25' reservations: memory: 256M cpus: '0.1' nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.prod.conf:/etc/nginx/nginx.conf - ssl_certs:/etc/nginx/ssl - static_files:/var/www/static - media_files:/var/www/media depends_on: - backend - frontend healthcheck: test: ["CMD", "curl", "-f", "https://localhost/health/"] interval: 30s timeout: 10s retries: 3 # Database Services postgres: image: postgres:15 restart: unless-stopped environment: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_INITDB_ARGS="--auth-host=scram-sha-256" volumes: - postgres_data:/var/lib/postgresql/data - ./backups:/backups - ./scripts:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 2G cpus: '1.0' reservations: memory: 1G cpus: '0.5' redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 3s retries: 3 deploy: resources: limits: memory: 512M cpus: '0.25' reservations: memory: 256M cpus: '0.1' # Monitoring and Logging prometheus: image: prom/prometheus:latest restart: unless-stopped ports: - "9090:9090" volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml - 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=200h' - '--web.enable-lifecycle' healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9090/-/healthy"] interval: 30s timeout: 10s retries: 3 grafana: image: grafana/grafana:latest restart: unless-stopped ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} - GF_USERS_ALLOW_SIGN_UP=false volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources depends_on: - prometheus healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 # Logging elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0 restart: unless-stopped environment: - discovery.type=single-node - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - xpack.security.enabled=false volumes: - elasticsearch_data:/usr/share/elasticsearch/data ports: - "9200:9200" healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"] interval: 10s timeout: 5s retries: 5 logstash: image: docker.elastic.co/logstash/logstash:8.8.0 restart: unless-stopped volumes: - ./monitoring/logstash/pipeline:/usr/share/logstash/pipeline depends_on: - elasticsearch healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9600"] interval: 30s timeout: 10s retries: 3 kibana: image: docker.elastic.co/kibana/kibana:8.8.0 restart: unless-stopped ports: - "5601:5601" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 depends_on: - elasticsearch healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5601/api/status"] interval: 30s timeout: 10s retries: 3 # Security fail2ban: image: crazymax/fail2ban:latest restart: unless-stopped cap_add: - NET_ADMIN - NET_RAW volumes: - ./fail2ban:/data - /var/log:/var/log:ro - backend_logs:/app/logs:ro environment: - F2B_DB_PURGE_AGE=30d - F2B_LOG_TARGET=/data/fail2ban.log healthcheck: test: ["CMD", "fail2ban-client", "ping"] interval: 30s timeout: 10s retries: 3 # Backup Services backup: image: alpine:latest restart: "no" volumes: - postgres_data:/data/postgres - redis_data:/data/redis - media_files:/data/media - ./backups:/backups - ./scripts/backup.sh:/backup.sh environment: - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET=${AWS_S3_BUCKET} - BACKUP_RETENTION_DAYS=30 entrypoint: /bin/sh command: /backup.sh # Worker Services celery: image: ghcr.io/${GITHUB_REPOSITORY:-malaysian-sme-platform}:latest-backend restart: unless-stopped command: celery -A backend worker -l info environment: - DEBUG=False - ENVIRONMENT=production - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} - REDIS_URL=redis://redis:6379/0 - SECRET_KEY=${SECRET_KEY} volumes: - backend_logs:/app/logs - media_files:/app/media depends_on: postgres: condition: service_healthy redis: condition: service_healthy deploy: replicas: 2 resources: limits: memory: 512M cpus: '0.25' reservations: memory: 256M cpus: '0.1' celery-beat: image: ghcr.io/${GITHUB_REPOSITORY:-malaysian-sme-platform}:latest-backend restart: unless-stopped command: celery -A backend beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler environment: - DEBUG=False - ENVIRONMENT=production - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} - REDIS_URL=redis://redis:6379/0 - SECRET_KEY=${SECRET_KEY} volumes: - backend_logs:/app/logs - media_files:/app/media depends_on: postgres: condition: service_healthy redis: condition: service_healthy flower: image: mher/flower:latest restart: unless-stopped environment: - CELERY_BROKER_URL=redis://redis:6379/0 - FLOWER_PORT=5555 ports: - "5555:5555" depends_on: - redis healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5555/"] interval: 30s timeout: 10s retries: 3 volumes: postgres_data: driver: local redis_data: driver: local backend_logs: driver: local static_files: driver: local media_files: driver: local ssl_certs: driver: local prometheus_data: driver: local grafana_data: driver: local elasticsearch_data: driver: local networks: default: driver: bridge ipam: config: - subnet: 172.20.0.0/16