Files
bot-pontos/docker-compose.yml
2026-10-30 23:25:38 -03:00

177 lines
5.0 KiB
YAML

# 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