name: ${COMPOSE_PROJECT_NAME:-zarinpal_analytics} x-logging: &bounded-logging driver: json-file options: max-size: 10m max-file: "3" x-backend-runtime: &backend-runtime image: zarinpal-analytics-backend:${IMAGE_TAG:-local} build: context: ./backend dockerfile: ../docker/backend.Dockerfile env_file: - ./backend/.env environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable REDIS_ADDR: redis:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} init: true restart: unless-stopped security_opt: - no-new-privileges:true logging: *bounded-logging services: postgres: image: postgres:18.4-alpine3.24 restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql networks: - data healthcheck: test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""] interval: 10s timeout: 3s retries: 5 start_period: 30s shm_size: 128mb mem_limit: 512m cpus: 0.75 pids_limit: 200 logging: *bounded-logging redis: image: redis:7.4.10-alpine3.21 restart: unless-stopped environment: REDIS_PASSWORD: ${REDIS_PASSWORD} command: - /bin/sh - -ec - | umask 077 printf 'appendonly yes\nappendfsync everysec\nmaxmemory 192mb\nmaxmemory-policy noeviction\nrequirepass %s\n' "$$REDIS_PASSWORD" > /tmp/redis.conf chown redis:redis /data /tmp/redis.conf exec su-exec redis redis-server /tmp/redis.conf volumes: - redis_data:/data networks: - data healthcheck: test: ["CMD-SHELL", "REDISCLI_AUTH=\"$$REDIS_PASSWORD\" redis-cli ping | grep -q PONG"] interval: 10s timeout: 3s retries: 5 start_period: 10s mem_limit: 256m cpus: 0.25 pids_limit: 100 security_opt: - no-new-privileges:true logging: *bounded-logging backend: <<: *backend-runtime command: ["/app/api"] depends_on: postgres: condition: service_healthy redis: condition: service_healthy expose: - "8080" networks: data: caddy_proxy: aliases: - zarinpal-backend healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"] interval: 10s timeout: 3s retries: 5 start_period: 30s stop_grace_period: 20s mem_limit: 256m cpus: 0.50 pids_limit: 150 worker: <<: *backend-runtime command: ["/app/worker"] depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - data healthcheck: test: ["CMD-SHELL", "kill -0 1"] interval: 10s timeout: 3s retries: 5 start_period: 30s stop_grace_period: 70s mem_limit: 512m cpus: 0.75 pids_limit: 200 frontend: image: zarinpal-analytics-frontend:${IMAGE_TAG:-local} build: context: ./frontend dockerfile: ../docker/frontend.Dockerfile init: true restart: unless-stopped expose: - "8080" networks: caddy_proxy: aliases: - zarinpal-frontend healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"] interval: 10s timeout: 3s retries: 5 start_period: 15s security_opt: - no-new-privileges:true mem_limit: 96m cpus: 0.25 pids_limit: 100 logging: *bounded-logging migrate: <<: *backend-runtime command: ["/app/migrate"] restart: "no" depends_on: postgres: condition: service_healthy networks: - data profiles: ["tools"] importer: <<: *backend-runtime command: ["/app/importer"] restart: "no" depends_on: postgres: condition: service_healthy volumes: - ${IMPORT_FILE:-/srv/zarinpal-data/challenge_data.csv.gz}:/import/challenge_data.csv.gz:ro networks: - data profiles: ["import"] admin: <<: *backend-runtime command: ["/app/admin"] restart: "no" depends_on: postgres: condition: service_healthy networks: - data profiles: ["tools"] networks: data: internal: true caddy_proxy: external: true name: ${CADDY_NETWORK:-caddy_proxy} volumes: postgres_data: redis_data: