Files
b2bcall/docker-compose.yml
B2BCall Bootstrap 6273b32214 feat: add frontend, nginx reverse proxy and monitoring/reports extras
- apps/frontend: Next.js 15 (App Router) + Tailwind v4 + componentes estilo
  shadcn/ui sobre Radix UI + TanStack Query. Tema light/dark, logo
  processada. Menu completo (secao 52) com gating por permissao real.
  Todas as telas do checklist de aceite (secao 90) conectadas a endpoints
  reais (nao mockup): login, usuarios, perfis/permissoes, ramais/troncos,
  dialplan, filas/agentes, console do agente, campanhas (CPS/CSV/
  iniciar/pausar), monitoramento ao vivo (polling, nao WebSocket real),
  TME/TMA, busca/export de chamadas, administracao do Asterisk, auditoria
- infrastructure/nginx: reverse proxy colocando frontend+API na mesma
  origem (porta 80), antecipado da Fase 9 pois a API nao publica porta
  propria
- apps/api: GET /api/monitoring/agents (estado corrente real via
  agent_state_events em aberto) e filtro queueId em GET /api/reports/calls

Pendencia registrada: tela de Callbacks nao implementada (schema existe
desde a Fase 6, mas nunca houve controller/service — construir a tela sem
API real seria mockup). Verificacao visual em navegador nao foi possivel
neste ambiente headless; validado via tsc/eslint/next build limpos + curl
reproduzindo as chamadas do navegador (middleware de auth, 24 paginas
protegidas via Nginx, endpoints de dados com cookie de sessao).
2026-08-27 17:35:34 -03:00

303 lines
8.4 KiB
YAML

name: b2bcall
networks:
b2bcall-net:
driver: bridge
volumes:
postgres-data:
redis-data:
asterisk-lib:
asterisk-log:
asterisk-spool:
# Compartilhado entre api e asterisk: a API gera o dialplan estruturado
# aqui (agente.md seção 21) e o Asterisk inclui via extensions.conf.
dialplan-generated:
services:
postgres:
image: postgres:17-alpine
container_name: b2bcall-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-b2bcall}
POSTGRES_USER: ${POSTGRES_USER:-b2bcall}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./infrastructure/postgres/init:/docker-entrypoint-initdb.d:ro
networks:
- b2bcall-net
ports:
# Publicado SOMENTE em loopback do host: o container do Asterisk usa
# network_mode: host (necessário para RTP) e por isso não enxerga a
# rede Docker interna por nome — precisa alcançar o Postgres via
# 127.0.0.1. Isso nunca fica acessível pela LAN/Internet (não é 0.0.0.0).
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-b2bcall} -d ${POSTGRES_DB:-b2bcall}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
redis:
image: redis:7-alpine
container_name: b2bcall-redis
restart: unless-stopped
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
command: >
redis-server
--requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
--maxmemory 100mb
--maxmemory-policy noeviction
--appendonly yes
volumes:
- redis-data:/data
networks:
- b2bcall-net
# Sem "ports": Redis não deve ficar acessível fora da rede Docker interna.
healthcheck:
test: ["CMD-SHELL", "redis-cli -a $$REDIS_PASSWORD --no-auth-warning ping | grep -q PONG"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 160M
asterisk:
build:
context: .
dockerfile: infrastructure/docker/asterisk.Dockerfile
image: b2bcall-asterisk:22.10.1
container_name: b2bcall-asterisk
restart: unless-stopped
network_mode: host
# RTP exige uma faixa larga de portas UDP — inviável de mapear uma a uma
# em bridge network, por isso host mode (ver docs/ARCHITECTURE.md 3.1).
# AMI (5038), ARI (8088) e SIP (5060) ficam protegidos pelo nftables
# (infrastructure/nftables/b2bcall.nft), que bloqueia acesso pela LAN.
depends_on:
postgres:
condition: service_healthy
env_file:
- .env
environment:
# O Asterisk (host network) alcança o Postgres via loopback do host,
# não pelo nome do container (ver publicação de porta do serviço postgres).
ASTERISK_DB_HOST: ${ASTERISK_DB_HOST:-127.0.0.1}
volumes:
- ./infrastructure/asterisk/config:/etc/asterisk-templates:ro
- asterisk-lib:/var/lib/asterisk
- asterisk-log:/var/log/asterisk
- asterisk-spool:/var/spool/asterisk
- dialplan-generated:/etc/asterisk-generated
healthcheck:
test: ["CMD-SHELL", "asterisk -rx 'core show uptime' | grep -q 'System uptime'"]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 400M
api:
build:
context: .
dockerfile: infrastructure/docker/api.Dockerfile
image: b2bcall-api:0.1.0
container_name: b2bcall-api
restart: unless-stopped
networks:
- b2bcall-net
# Permite alcançar o Asterisk (network_mode: host) via AMI/ARI a partir
# da rede bridge — ver docs/ARCHITECTURE.md 3.2.1.
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
env_file:
- .env
environment:
POSTGRES_HOST: postgres
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
volumes:
- dialplan-generated:/etc/asterisk-generated
# Sem "ports": só o Nginx (Fase 9) expõe HTTP ao mundo externo. Em dev,
# acessar via `docker compose exec` ou publicar temporariamente.
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 300M
asterisk-events:
build:
context: .
dockerfile: infrastructure/docker/asterisk-events.Dockerfile
image: b2bcall-asterisk-events:0.1.0
container_name: b2bcall-asterisk-events
restart: unless-stopped
networks:
- b2bcall-net
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
asterisk:
condition: service_healthy
env_file:
- .env
environment:
ASTERISK_HOST: host.docker.internal
POSTGRES_HOST: postgres
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 150M
frontend:
build:
context: .
dockerfile: infrastructure/docker/frontend.Dockerfile
image: b2bcall-frontend:0.1.0
container_name: b2bcall-frontend
restart: unless-stopped
networks:
- b2bcall-net
depends_on:
api:
condition: service_healthy
# Sem "ports": só o Nginx expõe HTTP ao mundo externo (agente.md seção
# 87) — o browser fala só com o Nginx, nunca direto com este container.
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/login', r => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 200M
nginx:
image: nginx:1.27-alpine
container_name: b2bcall-nginx
restart: unless-stopped
networks:
- b2bcall-net
depends_on:
frontend:
condition: service_healthy
api:
condition: service_healthy
volumes:
- ./infrastructure/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
# Único serviço exposto à LAN (agente.md seção 87) — todo o resto
# (api, frontend, postgres, redis, asterisk AMI/ARI) fica só na rede
# interna do Docker ou protegido por nftables.
- "80:80"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:80/login"]
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 64M
dialer-worker:
build:
context: .
dockerfile: infrastructure/docker/dialer-worker.Dockerfile
image: b2bcall-dialer-worker:0.1.0
container_name: b2bcall-dialer-worker
restart: unless-stopped
networks:
- b2bcall-net
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
asterisk:
condition: service_healthy
env_file:
- .env
environment:
ASTERISK_HOST: host.docker.internal
POSTGRES_HOST: postgres
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 150M