Debug de ligação à base de dados (Docker vs host)
Runbook curto: runbooks/postgres.md. Env: env-container.md.
Se a aplicação funciona com npm run start:dev no Windows mas dá ECONNREFUSED dentro do Docker, segue este guia.
0. Logs dizem DATABASE_URL … ausente e TypeORM host: localhost
Isso significa que dentro do contentor o processo Node não tem DATABASE_URL / POSTGRES_URL nem POSTGRES_HOST no ambiente. Não é firewall: a app nem sabe para onde ligar e cai nos defaults (localhost:5432).
Causas típicas
- Correr
docker runsem--env-file .ENVe sem-e DATABASE_URL=…. - Correr
docker composenoutra pasta onde não existe o ficheiro.ENVreferenciado nocompose.yaml. - Orquestrador (K8s, Portainer, etc.) a usar só a imagem sem injetar as mesmas variáveis.
O que fazer
- Na pasta do repositório (onde estão
compose.yamle.ENV):docker compose up --build. - Confirmar:
docker exec crm-backend printenv | grep DATABASE— tem de aparecerDATABASE_URL(e idealmenteAUTH_DATABASE_URL).
Se ainda assim não houver URL nem host de BD, a app termina com erro. Como paliativo, o projeto pode preencher DATABASE_URL / AUTH_DATABASE_URL (e JWT_SECRET se faltar) a partir de src/config/database-embedded-defaults.ts só quando o ambiente não define nada — vê aviso [Bootstrap] Sem config de BD… embutidos.
Contentor: crm-backend (definido em compose.yaml).
Postgres remoto (este projeto): portas 5455 (base principal) e 5440 (auth / perfis), não a 5432 por defeito.
Substitui SEU_IP_POSTGRES nos comandos pelo IP ou hostname real do servidor (ex.: o que usas em DATABASE_URL / AUTH_DATABASE_URL).
1. Variáveis de ambiente no contentor
Confirma se o Compose injetou DATABASE_URL, AUTH_DATABASE_URL, etc. (via env_file: .ENV).
docker exec -it crm-backend printenv | grep -E 'DATABASE|AUTH_DATABASE'
- Vazio → rever
env_filee o caminho do ficheiro.ENVna raiz do projeto, ou se o contentor corre com outro nome/imagem desatualizada.
2. Teste TCP com Node (Alpine)
A imagem de produção é node:20-alpine; podes não ter nc/telnet. Usa o Node já incluído:
Base principal (porta 5455):
docker exec -it crm-backend node -e "require('net').connect({host:'SEU_IP_POSTGRES',port:5455,family:4},()=>{console.log('5455 OK');process.exit(0)}).on('error',e=>{console.error('5455:',e.message);process.exit(1)})"
Auth / perfis (porta 5440):
docker exec -it crm-backend node -e "require('net').connect({host:'SEU_IP_POSTGRES',port:5440,family:4},()=>{console.log('5440 OK');process.exit(0)}).on('error',e=>{console.error('5440:',e.message);process.exit(1)})"
3. Interpretação dos resultados
| Resultado TCP | Causa provável | O que fazer |
|---|---|---|
| OK | Rede até ao Postgres existe; falha pode ser credenciais, SSL ou config TypeORM/Nest. | Ver logs de arranque [DatabaseEnv] (host/porta e parse da URL). Confirma URLs no .ENV. |
ECONNREFUSED | Nada aceitou TCP nesse host:porta a partir do contentor. | Whitelist em firewall/pg_hba, ou NAT Docker com IP não autorizado. |
ETIMEDOUT | Tráfego bloqueado ou rota errada. | Firewall, security group, VPN só no host sem passar pelo Docker. |
4. Comparar IP “visto lá fora” (host vs contentor)
Útil quando o servidor Postgres só aceita certos IPs de origem.
Windows (PowerShell) — usa curl.exe para não cair no alias de Invoke-WebRequest:
curl.exe -s https://ifconfig.me
Dentro do contentor (Alpine sem curl por defeito):
docker exec -it crm-backend sh -c "apk add --no-cache curl && curl -s https://ifconfig.me"
Se os IPs forem diferentes, o administrador da BD/firewall pode precisar de autorizar também o IP de saída do Docker.
5. Notas importantes
localhost/127.0.0.1no.ENVdentro do Docker apontam para o próprio contentor, não para o teu PC nem para um Postgres noutra máquina. Para serviços externos usa IP ou hostname redeável.- Interpolação no Compose:
environment: DATABASE_URL: ${DATABASE_URL}depende de um ficheiro.envna raiz (minúsculo) para o Compose substituir variáveis. Este projeto usaenv_file: .ENV; não é obrigatório duplicar no YAML se oprintenvjá mostrar as variáveis. - Linux: se o host liga mas o contentor não, em alguns cenários ajuda
network_mode: hostno serviço (não é o mesmo comportamento no Docker Desktop Windows). - Após alterar código de arranque ou
.ENV, fazdocker compose builde sobe de novo o serviço para o contentor refletir a imagem e o env atuais.
6. Logs detalhados na aplicação (VERBOSE_BOOT)
Em qualquer ambiente (incluindo Docker com NODE_ENV=production), define:
VERBOSE_BOOT=1
Valores aceites também: DEBUG_DB=1 ou LOG_DB_TARGET=1 (ver src/config/verbose-boot.util.ts).
Efeito:
Bootstrap: quais ficheiros.env/.ENVexistiram e foram lidos;PORT/NODE_ENV; mensagens antes/depois deNestFactory.create.DatabaseEnv: para cada BD — host, porta bruta, nome da base, utilizador, SSL, se a password está vazia ou o comprimento (nunca o valor); aviso se a porta não é número; em modo verbose tambémcwd, versão do Node,PGHOST/PGPORT, e dica se a URL existe mas o parse falhou.TypeORM: snapshot por data source (defaulteuserProfilesConnection) com os mesmos dados que o driverpgrecebe; aviso explícito se o host forlocalhostdentro do Docker.
Sem VERBOSE_BOOT, os logs DatabaseEnv e TypeORM continuam sempre ativos (só não aparecem os extras Bootstrap + bloco [verbose]).
7. Referências no código
- Compose:
compose.yaml(container_name: crm-backend,env_file: .ENV). - Logs:
src/main.ts(Bootstrap),src/config/database.config.ts(DatabaseEnv,logTypeOrmBootSnapshot),src/config/verbose-boot.util.ts.