# Use the Docker Compose build pack, with this file as its compose location. # Coolify owns generated URLs and secrets; never commit a rendered .env. x-environment: &environment NODE_ENV: production DATABASE_URL: postgres://manyangles:${SERVICE_PASSWORD_64_POSTGRES}@postgres:5432/manyangles NEXT_PUBLIC_APP_URL: ${SERVICE_URL_WEB} BETTER_AUTH_URL: ${SERVICE_URL_WEB} BETTER_AUTH_SECRET: ${SERVICE_PASSWORD_64_AUTH} S3_ENDPOINT: https://nyc3.digitaloceanspaces.com S3_PUBLIC_ENDPOINT: https://nyc3.digitaloceanspaces.com S3_REGION: nyc3 S3_BUCKET: hltma S3_ACCESS_KEY: ${SPACES_ACCESS_KEY:?Set Spaces access key} S3_SECRET_KEY: ${SPACES_SECRET_KEY:?Set Spaces secret key} S3_FORCE_PATH_STYLE: "false" EMAIL_PROVIDER: resend EMAIL_FROM: ${EMAIL_FROM} RESEND_API_KEY: ${RESEND_API_KEY} RESEND_WEBHOOK_SECRET: ${RESEND_WEBHOOK_SECRET} WORKER_CONCURRENCY: "1" x-runtime: &runtime restart: unless-stopped init: true security_opt: ["no-new-privileges:true"] cap_drop: [ALL] services: postgres: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_DB: manyangles POSTGRES_USER: manyangles POSTGRES_PASSWORD: ${SERVICE_PASSWORD_64_POSTGRES} volumes: ["postgres-data:/var/lib/postgresql/data"] healthcheck: test: ["CMD-SHELL", "pg_isready -U manyangles -d manyangles"] interval: 5s timeout: 3s retries: 20 garage: build: {context: ., target: garage} restart: unless-stopped command: ["/garage", "-c", "/etc/garage.toml", "server", "--single-node", "--default-bucket"] environment: SERVICE_URL_GARAGE_3900: ${SERVICE_URL_GARAGE_3900} GARAGE_RPC_SECRET: ${SERVICE_HEX_64_RPC} GARAGE_ADMIN_TOKEN: ${SERVICE_PASSWORD_64_GARAGEADMIN} GARAGE_DEFAULT_ACCESS_KEY: GK${SERVICE_HEX_24_S3KEY} GARAGE_DEFAULT_SECRET_KEY: ${SERVICE_HEX_64_S3SECRET} GARAGE_DEFAULT_BUCKET: manyangles expose: ["3900"] volumes: - garage-meta:/var/lib/garage/meta - garage-data:/var/lib/garage/data healthcheck: test: ["CMD", "/garage", "-c", "/etc/garage.toml", "status"] interval: 10s timeout: 5s retries: 10 migrate: build: {context: ., target: migrate} environment: DATABASE_URL: postgres://manyangles:${SERVICE_PASSWORD_64_POSTGRES}@postgres:5432/manyangles restart: "no" depends_on: postgres: {condition: service_healthy} storage-init: build: {context: ., target: worker} command: ["bun", "packages/storage/src/configure.ts"] # Keep the previous store available for rollback. Spaces CORS/lifecycle # are managed separately; its scoped object key cannot change bucket rules. environment: NEXT_PUBLIC_APP_URL: ${SERVICE_URL_WEB} S3_ENDPOINT: http://garage:3900 S3_REGION: garage S3_BUCKET: manyangles S3_ACCESS_KEY: GK${SERVICE_HEX_24_S3KEY} S3_SECRET_KEY: ${SERVICE_HEX_64_S3SECRET} restart: "no" depends_on: garage: {condition: service_healthy} web: <<: *runtime build: context: . target: web args: NEXT_PUBLIC_APP_URL: ${SERVICE_URL_WEB} environment: <<: *environment SERVICE_URL_WEB_3000: ${SERVICE_URL_WEB_3000} expose: ["3000"] depends_on: migrate: {condition: service_completed_successfully} storage-init: {condition: service_completed_successfully} healthcheck: test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3000/api/health/ready',{signal:AbortSignal.timeout(4000)}).then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 30s timeout: 5s start_period: 20s retries: 3 worker: <<: *runtime build: {context: ., target: worker} environment: <<: *environment WORKER_HEALTH_PORT: "3001" healthcheck: test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3001/health',{signal:AbortSignal.timeout(4000)}).then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 30s timeout: 5s start_period: 30s retries: 3 depends_on: migrate: {condition: service_completed_successfully} storage-init: {condition: service_completed_successfully} volumes: postgres-data: garage-meta: garage-data: