services: gateway: image: caddy:2.10-alpine restart: unless-stopped init: true read_only: true security_opt: - no-new-privileges:true tmpfs: - /tmp environment: PUBLIC_DOMAIN: ${PUBLIC_DOMAIN:?set PUBLIC_DOMAIN} TLS_EMAIL: ${TLS_EMAIL:?set TLS_EMAIL} ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config depends_on: frontend: condition: service_healthy auth: condition: service_healthy api: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q -O - --header=\"Host: $$PUBLIC_DOMAIN\" http://127.0.0.1/ >/dev/null"] interval: 15s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" frontend: build: context: .. dockerfile: apps/frontend/Dockerfile args: VITE_FRONTEND_URL: ${PUBLIC_URL:?set PUBLIC_URL} VITE_AUTH_URL: ${PUBLIC_URL:?set PUBLIC_URL} VITE_API_URL: ${PUBLIC_URL:?set PUBLIC_URL} VITE_DEV_MAILBOX_ENABLED: "false" image: productier/frontend:latest restart: unless-stopped init: true read_only: true security_opt: - no-new-privileges:true tmpfs: - /tmp environment: NODE_ENV: production PORT: 3000 depends_on: auth: condition: service_healthy api: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3000"] interval: 15s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" auth: build: context: ../apps/backend/auth-service dockerfile: Dockerfile image: productier/auth-service:latest restart: unless-stopped init: true read_only: true security_opt: - no-new-privileges:true tmpfs: - /tmp environment: APP_ENV: production NODE_ENV: production AUTH_PORT: 3001 FRONTEND_URL: ${PUBLIC_URL:?set PUBLIC_URL} AUTH_URL: ${PUBLIC_URL:?set PUBLIC_URL} DATABASE_URL: postgres://productier:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/productier?sslmode=disable BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET} CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:?set CORS_ALLOW_ORIGINS} AUTH_MAGIC_LINK_PROVIDER: ${AUTH_MAGIC_LINK_PROVIDER:-smtp} AUTH_MAIL_FROM: ${AUTH_MAIL_FROM:?set AUTH_MAIL_FROM} AUTH_SMTP_HOST: ${AUTH_SMTP_HOST:?set AUTH_SMTP_HOST} AUTH_SMTP_PORT: ${AUTH_SMTP_PORT:-587} AUTH_SMTP_SECURE: ${AUTH_SMTP_SECURE:-false} AUTH_SMTP_USER: ${AUTH_SMTP_USER:?set AUTH_SMTP_USER} AUTH_SMTP_PASSWORD: ${AUTH_SMTP_PASSWORD:?set AUTH_SMTP_PASSWORD} AUTH_DEV_MAILBOX_ENABLED: "false" depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3001/health"] interval: 15s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" api: build: context: ../apps/backend dockerfile: Dockerfile image: productier/api:latest restart: unless-stopped init: true read_only: true security_opt: - no-new-privileges:true tmpfs: - /tmp environment: APP_ENV: production API_PORT: 8080 API_SHUTDOWN_TIMEOUT: 15s DATABASE_URL: postgres://productier:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/productier?sslmode=disable AUTH_SERVICE_URL: http://auth:3001 CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:?set CORS_ALLOW_ORIGINS} BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET} MAIL_ENCRYPTION_KEY: ${MAIL_ENCRYPTION_KEY:?set MAIL_ENCRYPTION_KEY} FILE_STORAGE_PROVIDER: s3 S3_ENDPOINT: http://rustfs:9000 S3_REGION: ${S3_REGION:-us-east-1} S3_BUCKET: ${S3_BUCKET:-productier} S3_ACCESS_KEY: ${S3_ACCESS_KEY:?set S3_ACCESS_KEY} S3_SECRET_KEY: ${S3_SECRET_KEY:?set S3_SECRET_KEY} S3_USE_PATH_STYLE: "true" DB_MIGRATIONS_DIR: /app/migrations depends_on: postgres: condition: service_healthy auth: condition: service_healthy rustfs: condition: service_healthy rustfs-init: condition: service_completed_successfully healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8080/v1/health"] interval: 15s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" postgres: image: postgres:17-alpine restart: unless-stopped init: true security_opt: - no-new-privileges:true environment: POSTGRES_DB: productier POSTGRES_USER: productier POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U productier -d productier"] interval: 10s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" rustfs: image: rustfs/rustfs@sha256:0725587f6fcca83c1898f321424327d6e6da5e01ea20382905dd258ed5af3be4 restart: unless-stopped init: true security_opt: - no-new-privileges:true environment: RUSTFS_VOLUMES: /data/rustfs RUSTFS_ADDRESS: 0.0.0.0:9000 RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001 RUSTFS_CONSOLE_ENABLE: "true" RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY:?set S3_ACCESS_KEY} RUSTFS_SECRET_KEY: ${S3_SECRET_KEY:?set S3_SECRET_KEY} volumes: - rustfs-data:/data healthcheck: test: ["CMD", "sh", "-c", "wget -q -O - http://127.0.0.1:9000/health >/dev/null"] interval: 15s timeout: 5s retries: 20 logging: driver: json-file options: max-size: "10m" max-file: "5" rustfs-init: image: amazon/aws-cli:2.27.42 restart: "no" depends_on: rustfs: condition: service_healthy environment: AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:?set S3_ACCESS_KEY} AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:?set S3_SECRET_KEY} AWS_DEFAULT_REGION: ${S3_REGION:-us-east-1} entrypoint: - /bin/sh - -lc - | set -eu endpoint="http://rustfs:9000" until aws --endpoint-url "$${endpoint}" s3api list-buckets >/dev/null 2>&1; do sleep 1 done aws --endpoint-url "$${endpoint}" s3api head-bucket --bucket ${S3_BUCKET:-productier} >/dev/null 2>&1 || \ aws --endpoint-url "$${endpoint}" s3api create-bucket --bucket ${S3_BUCKET:-productier} volumes: caddy-data: caddy-config: postgres-data: rustfs-data: