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