# Bot de Pontos v2 — Docker Compose (ARQUITETURA.md, seção 11). # Antes de subir: criar o .env (a partir do .env.example) e os arquivos em ./secrets (ver README). name: botpontos x-backend: &backend build: context: ./backend image: botpontos-backend:dev restart: unless-stopped read_only: true cap_drop: [ALL] security_opt: ["no-new-privileges:true"] x-backend-env: &backend-env BP_LOG_LEVEL: ${BP_LOG_LEVEL:-info} BP_DEFAULT_TIMEZONE: ${BP_DEFAULT_TIMEZONE:-America/Sao_Paulo} BP_DB_HOST: postgres BP_DB_NAME: botpontos BP_DB_USER: botpontos_app BP_DB_PASSWORD_FILE: /run/secrets/pg_app_password BP_ENCRYPTION_KEY_FILE: /run/secrets/encryption_key BP_ENCRYPTION_KEY_VERSION: ${BP_ENCRYPTION_KEY_VERSION:-1} BP_TELEGRAM_TOKEN_FILE: /run/secrets/telegram_token BP_HOLIDAY_API_KEY_FILE: /run/secrets/holiday_api_key BP_HOLIDAY_LOCATION: ${BP_HOLIDAY_LOCATION:-3106200} BP_PUBLIC_URL: ${BP_PUBLIC_URL:-} BP_COOKIE_SECURE: ${BP_COOKIE_SECURE:-true} BP_TRUSTED_PROXIES: 127.0.0.0/8,172.16.0.0/12,${BP_NPM_IP:-} x-backend-secrets: &backend-secrets - pg_app_password - encryption_key - telegram_token - holiday_api_key services: postgres: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_DB: botpontos POSTGRES_USER: botpontos POSTGRES_PASSWORD_FILE: /run/secrets/pg_password volumes: - pgdata:/var/lib/postgresql/data - ./postgres/init:/docker-entrypoint-initdb.d:ro secrets: [pg_password, pg_app_password] healthcheck: test: ["CMD-SHELL", "pg_isready -U botpontos -d botpontos"] interval: 10s timeout: 5s retries: 10 networks: [internal] # Aplica as migrations com o usuário dono do banco e termina. migrate: <<: *backend restart: "no" command: ["migrate", "up"] environment: BP_LOG_LEVEL: ${BP_LOG_LEVEL:-info} BP_DB_HOST: postgres BP_DB_NAME: botpontos BP_DB_USER: botpontos BP_DB_PASSWORD_FILE: /run/secrets/pg_password secrets: [pg_password] depends_on: postgres: condition: service_healthy networks: [internal] api: <<: *backend command: ["api"] environment: <<: *backend-env BP_DB_MAX_CONNS: "10" secrets: *backend-secrets depends_on: migrate: condition: service_completed_successfully healthcheck: test: ["CMD", "/botpontos", "healthcheck", "http://127.0.0.1:8080/api/v1/health/live"] interval: 15s timeout: 5s retries: 3 # egress: a API valida credenciais na PontoMais, reenvia lembretes e sincroniza feriados networks: [internal, egress] worker: <<: *backend command: ["worker"] environment: <<: *backend-env BP_DB_MAX_CONNS: "5" secrets: *backend-secrets depends_on: migrate: condition: service_completed_successfully healthcheck: test: ["CMD", "/botpontos", "healthcheck", "http://127.0.0.1:8081/health/live"] interval: 15s timeout: 5s retries: 3 networks: [internal, egress] web: build: context: ./web args: API_INTERNAL_URL: http://api:8080 image: botpontos-web:dev restart: unless-stopped environment: API_INTERNAL_URL: http://api:8080 depends_on: api: condition: service_healthy # Publicado só no IP de rede local do servidor. O Nginx Proxy Manager (outra # máquina) aponta para cá, e o firewall libera a 3000 só para o IP do NPM # (deploy/botpontos-firewall.service). Em desenvolvimento, use 127.0.0.1. ports: - "${BP_HOST_LAN_IP:?defina BP_HOST_LAN_IP no arquivo .env}:${BP_WEB_PORT:-3000}:3000" read_only: true tmpfs: - /tmp - /app/.next/cache cap_drop: [ALL] security_opt: ["no-new-privileges:true"] healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 15s timeout: 5s retries: 3 networks: [internal, ingress] backup: image: postgres:17-alpine restart: unless-stopped entrypoint: ["sh", "/scripts/backup-loop.sh"] environment: PGHOST: postgres PGUSER: botpontos PGDATABASE: botpontos BACKUP_INTERVAL_SECONDS: "86400" BACKUP_RETENTION_DAYS: "14" volumes: - ./backups:/backups - ./scripts:/scripts:ro secrets: [pg_password] depends_on: postgres: condition: service_healthy networks: [internal] volumes: pgdata: networks: internal: internal: true # sem acesso à internet; só comunicação entre containers egress: {} # saída para PontoMais, Telegram e feriadosapi.com ingress: {} # rede da porta publicada do web secrets: pg_password: file: ./secrets/pg_password pg_app_password: file: ./secrets/pg_app_password encryption_key: file: ./secrets/encryption_key telegram_token: file: ./secrets/telegram_token holiday_api_key: file: ./secrets/holiday_api_key # pode ficar vazio: vale o fallback de segunda a sexta