version: '3.8' services: postgres: image: docker.arvancloud.ir/postgres:15-alpine container_name: lucky_wheel_postgres environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgrespassword} POSTGRES_DB: ${POSTGRES_DB:-lucky_wheel} ports: - "${EXTERNAL_DB_PORT:-5432}:5432" volumes: - pg_data:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-lucky_wheel}"] interval: 5s timeout: 5s retries: 12 redis: image: docker.arvancloud.ir/redis:7-alpine container_name: lucky_wheel_redis restart: unless-stopped volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 5 api: build: context: ./apps/api dockerfile: Dockerfile container_name: lucky_wheel_api # Prisma connects on module init, so a not-yet-ready DB crash-loops the API. depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: # Always built from the same POSTGRES_* vars as the db service. # A separate DATABASE_URL in Dokploy (e.g. password "postgres" from another # stack) was authenticating as user postgres with the wrong password (P1000). - DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgrespassword}@postgres:5432/${POSTGRES_DB:-lucky_wheel}?schema=public - PORT=${PORT:-3000} - NODE_ENV=production - REDIS_HOST=redis - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD:-} - JWT_USER_SECRET=${JWT_USER_SECRET:-lucky_wheel_user_jwt_super_secret_key_2026!} - JWT_USER_EXPIRES_IN=${JWT_USER_EXPIRES_IN:-7d} - JWT_ADMIN_ACCESS_SECRET=${JWT_ADMIN_ACCESS_SECRET:-lucky_wheel_admin_access_jwt_super_secret_key_2026!} - JWT_ADMIN_ACCESS_EXPIRES_IN=${JWT_ADMIN_ACCESS_EXPIRES_IN:-1h} - JWT_ADMIN_REFRESH_SECRET=${JWT_ADMIN_REFRESH_SECRET:-lucky_wheel_admin_refresh_jwt_super_secret_key_2026!} - JWT_ADMIN_REFRESH_EXPIRES_IN=${JWT_ADMIN_REFRESH_EXPIRES_IN:-7d} - CORS_ADMIN_ORIGIN=${CORS_ADMIN_ORIGIN:-http://localhost:5173} - CORS_CLIENT_ORIGIN=${CORS_CLIENT_ORIGIN:-http://localhost:5174} - STORAGE_DRIVER=${STORAGE_DRIVER:-local} - STORAGE_LOCAL_ROOT=${STORAGE_LOCAL_ROOT:-./uploads} - STORAGE_PUBLIC_URL=${STORAGE_PUBLIC_URL:-} - SMS_DRIVER=${SMS_DRIVER:-mock} - RAYGAN_SMS_USERNAME=${RAYGAN_SMS_USERNAME:-} - RAYGAN_SMS_PASSWORD=${RAYGAN_SMS_PASSWORD:-} - RAYGAN_SMS_LINE_NUMBER=${RAYGAN_SMS_LINE_NUMBER:-} - RAYGAN_SMS_API_BASE_URL=${RAYGAN_SMS_API_BASE_URL:-https://api.raygansms.com} # No host ports — Dokploy/Traefik routes by domain to container port 3000 expose: - "3000" restart: unless-stopped # The bare name "api" is ambiguous on Dokploy's shared network (other stacks # register it too), which made nginx dial the wrong containers. Use a unique alias. networks: default: aliases: - luckywheel-api admin: build: context: ./apps/admin dockerfile: Dockerfile container_name: lucky_wheel_admin # No host ports — Dokploy/Traefik routes by domain to container port 80 expose: - "80" restart: unless-stopped depends_on: - api client: build: context: ./apps/client dockerfile: Dockerfile container_name: lucky_wheel_client # No host ports — Dokploy/Traefik routes by domain to container port 80 expose: - "80" restart: unless-stopped depends_on: - api volumes: pg_data: redis_data: