version: "3.8" x-app-environment: &app-environment APP_ENV: production DATABASE_HOST: db DATABASE_NAME: dtf DATABASE_USER: dtf_app # Must differ from POSTGRES_PASSWORD: the runtime role is DML-only, and reusing # the administrator credential would make that restriction meaningless. DATABASE_PASSWORD: ${APP_DB_PASSWORD:?set APP_DB_PASSWORD} S3_ENDPOINT: ${R2_ENDPOINT:?set R2_ENDPOINT} S3_PUBLIC_ENDPOINT: ${R2_ENDPOINT:?set R2_ENDPOINT} S3_BUCKET: ${R2_BUCKET:?set R2_BUCKET} AWS_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:?set R2_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:?set R2_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION: auto # Optional at deploy time so a missing Kanban credential cannot make the # entire Swarm stack invalid. The Kanban login endpoint fails closed until # this value is configured. OPERATOR_EMAIL: ${OPERATOR_EMAIL:-} OPERATOR_PASSWORD: ${OPERATOR_PASSWORD:?set OPERATOR_PASSWORD} PAYMENT_ADAPTER: fake # Optional: without it the webhook verifies nothing and therefore accepts # nothing, which is the correct state until a provider is connected. Set it # when the provider is configured, never to a value anyone could guess. PAYMENT_WEBHOOK_SECRET: ${PAYMENT_WEBHOOK_SECRET:-} FREIGHT_ADAPTER: fake # Order creation in Tiny stays off until it has been tested against the # client's account (Tiny has no sandbox). The application credentials can be # set now: they let an operator connect Tiny from the Kanban, and the worker # keeps that connection alive. Callback: https:///api/operator/tiny/callback TINY_ADAPTER: fake TINY_CLIENT_ID: ${TINY_CLIENT_ID:-} TINY_CLIENT_SECRET: ${TINY_CLIENT_SECRET:-} TINY_REDIRECT_URI: ${TINY_REDIRECT_URI:-} TINY_PRODUCT_TEXTIL_FOLHA: ${TINY_PRODUCT_TEXTIL_FOLHA:-} TINY_PRODUCT_TEXTIL_AVULSA: ${TINY_PRODUCT_TEXTIL_AVULSA:-} TINY_PRODUCT_UV_FOLHA: ${TINY_PRODUCT_UV_FOLHA:-} TINY_PRODUCT_UV_AVULSA: ${TINY_PRODUCT_UV_AVULSA:-} WHATSAPP_ADAPTER: fake STORAGE_ADAPTER: s3-r2 PUBLIC_ORIGIN: https://${SITE_DOMAIN:?set SITE_DOMAIN} PUBLIC_HOST: ${SITE_DOMAIN:?set SITE_DOMAIN} ALLOWED_HOSTS: ${SITE_DOMAIN:?set SITE_DOMAIN},${KANBAN_DOMAIN:?set KANBAN_DOMAIN} ALLOWED_ORIGINS: https://${SITE_DOMAIN:?set SITE_DOMAIN},https://${KANBAN_DOMAIN:?set KANBAN_DOMAIN} COOKIE_SECURE: "true" MAX_UPLOAD_BYTES: "5368709120" UPLOAD_PART_BYTES: "8388608" STORAGE_QUOTA_BYTES: "53687091200" OWNER_UPLOAD_QUOTA_BYTES: "10737418240" MAX_PENDING_UPLOADS: "10" SCAN_MAX_BYTES: "134217728" services: db: image: postgres:17-alpine environment: POSTGRES_DB: dtf POSTGRES_USER: dtf_admin POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} volumes: [postgres-data:/var/lib/postgresql/data] networks: [backend] healthcheck: test: [CMD-SHELL, 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'] interval: 10s timeout: 5s retries: 12 start_period: 20s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 10s} db-init: image: ${API_IMAGE:-gitea.blyzer.com.br/blyzer/dtf-api}:${IMAGE_TAG:-latest} command: python -m app.bootstrap environment: DATABASE_ADMIN_HOST: db DATABASE_ADMIN_NAME: dtf DATABASE_ADMIN_USER: dtf_admin DATABASE_ADMIN_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} APP_DB_USER: dtf_app APP_DB_PASSWORD: ${APP_DB_PASSWORD:?set APP_DB_PASSWORD} # The migration job seeds the first operator account from these, so an # existing deployment keeps its Kanban login after the accounts table # lands. Without them there would be no account at all and login would # fail closed with 503. OPERATOR_EMAIL: ${OPERATOR_EMAIL:-} OPERATOR_PASSWORD: ${OPERATOR_PASSWORD:?set OPERATOR_PASSWORD} networks: [backend] deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 5s, max_attempts: 20} scanner: image: clamav/clamav@sha256:9cb27d7660bdf66e9878c832cb433dd8aa152cfbe16f3c2c0084c80b04ae22b4 entrypoint: [clamd, --foreground=true, --config-file=/etc/clamav/clamd.conf] configs: - source: clamd_config target: /etc/clamav/clamd.conf mode: 0444 networks: [backend] healthcheck: test: [CMD, clamdscan, --config-file=/etc/clamav/clamd.conf, --ping, "3"] interval: 15s timeout: 5s retries: 20 start_period: 90s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 10s} resources: limits: {memory: 3G} api: image: ${API_IMAGE:-gitea.blyzer.com.br/blyzer/dtf-api}:${IMAGE_TAG:-latest} environment: *app-environment networks: [backend, egress] read_only: true tmpfs: [/tmp] healthcheck: test: [CMD-SHELL, 'python -c "import os,urllib.request; r=urllib.request.Request(\"http://localhost:8000/health\",headers={\"Host\":os.environ[\"PUBLIC_HOST\"]}); urllib.request.urlopen(r,timeout=3)"'] interval: 15s timeout: 5s retries: 12 start_period: 30s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 5s} worker: image: ${API_IMAGE:-gitea.blyzer.com.br/blyzer/dtf-api}:${IMAGE_TAG:-latest} command: python -m app.worker environment: *app-environment networks: [backend, egress] read_only: true tmpfs: [/tmp] healthcheck: test: [CMD, python, -c, "import urllib.request; urllib.request.urlopen('http://localhost:8002/health',timeout=3)"] interval: 15s timeout: 5s retries: 12 start_period: 90s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 5s} site: image: ${WEB_IMAGE:-gitea.blyzer.com.br/blyzer/dtf-web}:${IMAGE_TAG:-latest} environment: WEB_INDEX: index.html PUBLIC_HOST: ${SITE_DOMAIN:?set SITE_DOMAIN} S3_PUBLIC_ENDPOINT: ${R2_ENDPOINT:?set R2_ENDPOINT} networks: [backend] ports: - target: 8080 published: ${SITE_PORT:-18080} protocol: tcp mode: ingress healthcheck: test: [CMD-SHELL, 'wget -q --header="Host: $$PUBLIC_HOST" -O /dev/null http://127.0.0.1:8080/health'] interval: 15s timeout: 5s retries: 12 start_period: 15s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 5s} kanban: image: ${WEB_IMAGE:-gitea.blyzer.com.br/blyzer/dtf-web}:${IMAGE_TAG:-latest} environment: WEB_INDEX: kanban.html PUBLIC_HOST: ${KANBAN_DOMAIN:?set KANBAN_DOMAIN} S3_PUBLIC_ENDPOINT: ${R2_ENDPOINT:?set R2_ENDPOINT} networks: [backend] ports: - target: 8080 published: ${KANBAN_PORT:-18081} protocol: tcp mode: ingress healthcheck: test: [CMD-SHELL, 'wget -q --header="Host: $$PUBLIC_HOST" -O /dev/null http://127.0.0.1:8080/health'] interval: 15s timeout: 5s retries: 12 start_period: 15s deploy: replicas: 1 restart_policy: {condition: on-failure, delay: 5s} configs: clamd_config: file: ./infra/clamd.conf volumes: postgres-data: networks: backend: driver: overlay internal: true egress: driver: overlay