Pular para o conteúdo principal

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 run sem --env-file .ENV e sem -e DATABASE_URL=….
  • Correr docker compose noutra pasta onde não existe o ficheiro .ENV referenciado no compose.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.yaml e .ENV): docker compose up --build.
  • Confirmar: docker exec crm-backend printenv | grep DATABASE — tem de aparecer DATABASE_URL (e idealmente AUTH_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_file e o caminho do ficheiro .ENV na 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 TCPCausa provávelO que fazer
OKRede 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.
ECONNREFUSEDNada aceitou TCP nesse host:porta a partir do contentor.Whitelist em firewall/pg_hba, ou NAT Docker com IP não autorizado.
ETIMEDOUTTrá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.1 no .ENV dentro 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 .env na raiz (minúsculo) para o Compose substituir variáveis. Este projeto usa env_file: .ENV; não é obrigatório duplicar no YAML se o printenv já mostrar as variáveis.
  • Linux: se o host liga mas o contentor não, em alguns cenários ajuda network_mode: host no serviço (não é o mesmo comportamento no Docker Desktop Windows).
  • Após alterar código de arranque ou .ENV, faz docker compose build e 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 / .ENV existiram e foram lidos; PORT / NODE_ENV; mensagens antes/depois de NestFactory.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ém cwd, versão do Node, PGHOST/PGPORT, e dica se a URL existe mas o parse falhou.
  • TypeORM: snapshot por data source (default e userProfilesConnection) com os mesmos dados que o driver pg recebe; aviso explícito se o host for localhost dentro 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.