Dicas para depurar variáveis de ambiente dentro de containers

1. Por que variáveis de ambiente são um ponto crítico em containers

As variáveis de ambiente são o mecanismo central de configuração em containers, seguindo os princípios da metodologia 12-factor app. Elas separam configuração de código, permitindo que a mesma imagem seja executada em diferentes ambientes (desenvolvimento, teste, produção) sem modificações. No entanto, essa flexibilidade traz desafios significativos.

Problemas comuns incluem erros de digitação nos nomes das variáveis, valores ausentes que quebram a aplicação silenciosamente, e valores incorretos que causam comportamentos inesperados. Em containers, a depuração é mais complexa porque não temos acesso direto ao sistema operacional completo — precisamos de técnicas específicas para inspecionar o ambiente de execução.

A diferença entre onde as variáveis são definidas (Dockerfile, docker-compose, orquestradores) adiciona outra camada de complexidade. Uma variável pode ser definida em múltiplos lugares, e entender qual valor prevalece é essencial para resolver problemas.

2. Inspeção básica: comandos essenciais dentro do container

O primeiro passo para depurar variáveis de ambiente é inspecionar o que está realmente disponível dentro do container. Os comandos mais úteis são:

# Listar todas as variáveis de ambiente
env

# Alternativa para sistemas mais minimalistas
printenv

# Verificar uma variável específica
echo $DATABASE_URL
echo ${DATABASE_URL}

# Verificar se a variável existe (com valor padrão)
echo ${DATABASE_URL:-"não definida"}

Para executar esses comandos em containers em execução:

# Docker
docker exec -it meu-container env
docker exec -it meu-container printenv
docker exec -it meu-container sh -c 'echo $DATABASE_URL'

# Kubernetes
kubectl exec -it meu-pod -- env
kubectl exec -it meu-pod -- printenv
kubectl exec -it meu-pod -- sh -c 'echo $DATABASE_URL'

Uma técnica valiosa é verificar o entrypoint e scripts de inicialização, pois muitas vezes as variáveis são processadas ou transformadas antes da aplicação principal ser iniciada:

# Verificar o entrypoint definido
docker inspect meu-container | grep -A 5 "Entrypoint"

# Executar o entrypoint manualmente para ver logs
docker run --rm -it minha-imagem sh -c 'cat /entrypoint.sh'

3. Depuração de variáveis ausentes ou com valor vazio

Variáveis ausentes ou vazias são uma das causas mais comuns de falhas em containers. Felizmente, existem técnicas eficientes para diagnosticar e testar esses cenários.

O shell oferece expansões condicionais que ajudam a testar variáveis:

# Usar valor padrão se a variável não estiver definida
echo ${PORT:-3000}

# Exibir erro se a variável não estiver definida (útil em scripts)
echo ${PORT:?"ERRO: PORT não definida"}

Para sobrescrever variáveis temporariamente durante testes:

# Sobrescrever variável ao executar o container
docker run --rm -e DATABASE_URL="postgres://teste:teste@localhost:5432/teste" minha-imagem

# Usar arquivo de ambiente para múltiplas variáveis
docker run --rm --env-file ./test.env minha-imagem

Uma técnica poderosa é adicionar logs de inicialização no entrypoint para capturar o estado das variáveis:

# Exemplo de script entrypoint.sh
#!/bin/sh
echo "=== Variáveis de ambiente carregadas ==="
echo "DATABASE_URL: ${DATABASE_URL:-NÃO DEFINIDA}"
echo "PORT: ${PORT:-3000}"
echo "NODE_ENV: ${NODE_ENV:-development}"
echo "========================================"

# Executar a aplicação principal
exec "$@"

4. Rastreando a origem das variáveis de ambiente

Entender a hierarquia de precedência é fundamental para resolver conflitos. A ordem de precedência (da maior para a menor) é:

  1. Comando docker run -e — Sobrescreve tudo
  2. Arquivo .env — Carregado pelo docker-compose
  3. Arquivo docker-compose.yml — Seção environment:
  4. Dockerfile ENV — Definido na construção da imagem
  5. Dockerfile ARG — Variáveis de build-time (não persistem no container final)

Para depurar sobreposição, use:

# Verificar o Dockerfile
docker history minha-imagem --no-trunc | grep ENV

# Verificar configuração do docker-compose
docker-compose config

# Verificar variáveis no momento da execução
docker inspect meu-container | jq '.[0].Config.Env'

A diferença entre ENV e ARG no Dockerfile é crucial:

# Dockerfile exemplo
ARG BUILD_VERSION=1.0
ENV APP_VERSION=${BUILD_VERSION}
ENV DATABASE_URL="postgres://localhost:5432/app"

# ARG só existe durante o build
# ENV persiste no container final

5. Usando ferramentas de diagnóstico dentro do container

Quando os comandos básicos não são suficientes, ferramentas especializadas ajudam a diagnosticar problemas mais complexos.

O envsubst é útil para verificar substituição de variáveis em arquivos de configuração:

# Instalar envsubst (se disponível)
apt-get update && apt-get install -y gettext-base

# Testar substituição em um template
echo "Server running on port ${PORT:-3000}" | envsubst

Para uma visão completa de todas as variáveis, incluindo as do processo inicial:

# Ver ambiente do processo PID 1
cat /proc/1/environ | tr '\0' '\n'

# Comparar com o ambiente do shell atual
env | sort > /tmp/env_shell.txt
cat /proc/1/environ | tr '\0' '\n' | sort > /tmp/env_init.txt
diff /tmp/env_shell.txt /tmp/env_init.txt

Para adicionar ferramentas temporariamente sem modificar a imagem:

# Copiar binário de diagnóstico para o container
docker cp /usr/bin/strace meu-container:/tmp/
docker exec -it meu-container /tmp/strace -e trace=execve -p 1

# Ou usar um container sidecar com ferramentas
docker run --rm --pid=container:meu-container alpine env

6. Depuração em ambientes orquestrados (Kubernetes/Docker Swarm)

Em ambientes orquestrados, as variáveis podem vir de ConfigMaps e Secrets, adicionando complexidade.

Para Kubernetes:

# Ver variáveis definidas no pod
kubectl describe pod meu-pod | grep -A 10 "Environment"

# Ver ConfigMaps e Secrets associados
kubectl get configmap meu-config -o yaml
kubectl get secret meu-secret -o yaml | base64 -d

# Executar comando dentro do pod
kubectl exec -it meu-pod -- sh -c 'env | sort'

A diferença entre envFrom e valueFrom:

# envFrom: importa todo o ConfigMap/Secret
envFrom:
  - configMapRef:
      name: app-config

# valueFrom: importa chaves específicas
env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: url

Para criar containers de diagnóstico temporários:

# Usar kubectl debug (Kubernetes 1.20+)
kubectl debug meu-pod -it --image=alpine -- sh

# Container sidecar para depuração
kubectl run debug-pod --image=alpine --rm -it --restart=Never -- sh

7. Estratégias para evitar problemas futuros

A melhor depuração é aquela que previne problemas antes que eles ocorram. Implemente estas estratégias:

Healthcheck que valida variáveis obrigatórias:

#!/bin/sh
# healthcheck.sh
REQUIRED_VARS="DATABASE_URL PORT API_KEY"
for var in $REQUIRED_VARS; do
  if [ -z "${!var}" ]; then
    echo "ERRO: Variável $var não definida"
    exit 1
  fi
done
echo "Todas as variáveis obrigatórias estão definidas"
exit 0

Arquivo .env.example documentado:

# .env.example
# Database
DATABASE_URL=postgres://user:password@localhost:5432/app
# Application
PORT=3000
NODE_ENV=development
# API
API_KEY=sua_chave_aqui

Testes unitários para diferentes cenários:

# test_env.sh
#!/bin/sh
echo "Testando cenário 1: Todas variáveis definidas"
export DATABASE_URL="postgres://test:test@localhost:5432/test"
export PORT=3000
./healthcheck.sh && echo "OK" || echo "FALHOU"

echo "Testando cenário 2: Porta ausente"
unset PORT
./healthcheck.sh && echo "OK" || echo "FALHOU"  # Deve falhar

Referências