# Stack local completa para desenvolvimento e VM Ubuntu. # # O Supabase local é gerenciado pelo `supabase start` e roda em Docker com os # serviços oficiais de Postgres, Auth, PostgREST, Storage e Realtime. Este # arquivo sobe os serviços da aplicação na mesma máquina, usando as portas # publicadas pelo Supabase local. # # Uso: bash scripts/local-stack.sh up services: app: build: context: . dockerfile: Dockerfile args: APP_VERSION: ${APP_VERSION:-local} image: deskcomm-app:local pull_policy: never restart: unless-stopped env_file: ${LOCAL_ENV_FILE:-.env.local} environment: WAHA_API_BASE_URL: http://waha:3000 # O `worker` abaixo drena o event_log; ver docker-compose.prod.yml. EVENT_LOG_WORKER_DRAINS: ${EVENT_LOG_WORKER_DRAINS:-true} ports: - "${APP_PORT:-3000}:3000" extra_hosts: - "host.docker.internal:host-gateway" depends_on: waha: condition: service_healthy srh: condition: service_started healthcheck: test: ["CMD", "node", "-e", "require('net').connect(3000,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))"] interval: 30s timeout: 5s retries: 5 start_period: 40s worker: build: context: . dockerfile: Dockerfile.worker args: APP_VERSION: ${APP_VERSION:-local} image: deskcomm-worker:local pull_policy: never restart: unless-stopped env_file: ${LOCAL_ENV_FILE:-.env.local} environment: WAHA_API_BASE_URL: http://waha:3000 extra_hosts: - "host.docker.internal:host-gateway" depends_on: waha: condition: service_healthy ports: # Só 127.0.0.1: /healthz e /metrics do worker não têm autenticação e o # /metrics conta filas, sessões e custo de todas as empresas. Sem endereço # de bind o Docker publica em 0.0.0.0 — qualquer um na mesma rede (Wi-Fi # do café, LAN do escritório) lia os números. O healthcheck roda DENTRO do # container e não depende desta linha. - "127.0.0.1:8787:8787" healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8787/healthz"] interval: 30s timeout: 5s retries: 3 start_period: 20s scheduler: build: context: . dockerfile: Dockerfile.scheduler args: APP_VERSION: ${APP_VERSION:-local} image: deskcomm-scheduler:local pull_policy: never restart: unless-stopped env_file: ${LOCAL_ENV_FILE:-.env.local} environment: TZ: UTC # Os crons autenticam com INTERNAL_CRON_SECRET quando definido; o app # aplica a mesma prioridade. Sem este fallback, o scheduler enviava # INTERNAL_SECRET e recebia 401 em instalações com os dois segredos. INTERNAL_SECRET: ${INTERNAL_CRON_SECRET:-${INTERNAL_SECRET}} depends_on: app: condition: service_healthy waha: image: devlikeapro/waha:latest-2026.7.2 # `platform` FIXADO por medição, não por hábito (#3032). # # O índice da tag acima publica UM arquivo só: `linux/amd64`. Medido em # 2026-10-23 com `docker manifest inspect` sobre essa tag: # # [{"architecture":"amd64","os":"linux"}, # {"architecture":"unknown","os":"unknown"}] # # A tag irmã, a de arm64, é o espelho disso — só `linux/arm64`. Nenhuma das # duas é multi-arch, então "trocar por uma tag multi-arch" não existe para # esta versão: o que resta é declarar aqui a arquitetura que a tag publica. # # Sem esta linha o Compose pede o manifesto da arquitetura DO HOST, e num # host arm64 (Mac Apple Silicon, VM arm64) o `up` morre em # # no matching manifest for linux/arm64/v8 in the manifest list entries # # — a stack local não sobe. Com a arquitetura declarada a resolução vale em # qualquer host: em arm64 o contêiner roda por emulação (Docker Desktop traz # Rosetta/qemu; numa VM arm64, o qemu-user estático registrado no binfmt). # É a mesma escolha que o `docker-compose.yml` da raiz já faz para a mesma # imagem, e o teste `tests/unit/plataforma-do-canal-no-compose-local.test.ts` # guarda que ela não suma. # # O `docker-compose.prod.yml` NÃO fixa plataforma de propósito: lá a imagem # vem de interpolação e o instalador grava a variante nativa da arquitetura # do host (arm64 recebe a tag publicada só para arm64) — fixar amd64 ali # quebraria o host arm64, e o pedido voltaria a não casar manifesto. platform: linux/amd64 restart: unless-stopped ports: # 127.0.0.1 e não 0.0.0.0: o dashboard do WAHA está ligado logo abaixo e # comanda a sessão de WhatsApp — ele tem autenticação própria, com padrão # do WAHA, que a chave da API não protege. Sem endereço de bind, qualquer # máquina da rede da VM alcançaria esse painel. `docker-compose.prod.yml` # resolve o mesmo problema desligando o dashboard ("nunca expor o # dashboard num template público", linha 156) e não publicando porta # nenhuma para o WAHA; aqui ele fica de pé para quem desenvolve, mas só # atende de dentro da própria máquina (de fora, `ssh -L 3030:localhost:3030`). - "127.0.0.1:3030:3000" environment: # O WAHA recebe o hash com o prefixo para hashear a chave plaintext # enviada pelo app em X-Api-Key. WAHA_API_KEY: "sha512:${WAHA_API_KEY_SHA512}" WHATSAPP_HOOK_URL: http://host.docker.internal:${APP_PORT:-3000}/api/v1/webhooks/waha # `message.reaction` entra: é por onde chega o 👍/😐 do cliente (#2797). # Mantido IGUAL ao `docker-compose.prod.yml` — o consumidor local de quem # desenvolve não pode ver menos eventos que a instalação real. WHATSAPP_HOOK_EVENTS: "message.any,message.ack,message.edited,message.revoked,message.reaction,session.status,state.change" # Nome EXATO da doc do WAHA (events#hmac-authentication): sem o _KEY ele ignora. WHATSAPP_HOOK_HMAC_KEY: ${WAHA_HMAC_SECRET} WHATSAPP_DEFAULT_ENGINE: NOWEB WAHA_DASHBOARD_ENABLED: "true" WHATSAPP_RESTART_ALL_SESSIONS: "True" extra_hosts: - "host.docker.internal:host-gateway" volumes: - waha-data:/app/.sessions - waha-media:/app/.media healthcheck: test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/ping"] interval: 30s timeout: 5s retries: 5 start_period: 40s redis: image: redis:7-alpine restart: unless-stopped command: ["redis-server", "--save", "", "--appendonly", "no"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 3s retries: 3 srh: image: hiett/serverless-redis-http@sha256:5b0bb9239fce53abf87b2018a7a0deb9ec7bd900c5360738fe5fbeeb426f9150 restart: unless-stopped environment: SRH_MODE: env SRH_TOKEN: ${SRH_TOKEN} SRH_CONNECTION_STRING: redis://redis:6379 depends_on: redis: condition: service_healthy volumes: waha-data: waha-media: