Já vi três incidentes de produção causados pelo mesmo erro: alguém commitou um .env com credenciais de banco no repositório, a imagem Docker empacotou o arquivo, e as variáveis vazaram no registry. Não era malware. Não era ataque. Era preguiça com env vars. O container subiu, funcionou, e ninguém percebeu até a auditoria de segurança seis meses depois.
Variáveis de ambiente parecem simples. Você define, a aplicação lê, fim. Mas em produção com Docker a coisa tem camadas: o arquivo .env local, o docker run --env, o env_file no Compose, o ENV no Dockerfile, os secrets do orquestrador. Cada um desses mecanismos tem um caso de uso diferente e, quando você mistura, o resultado é bug silencioso ou vazamento.
Este guia organiza quando usar cada mecanismo e mostra como configurar env vars em Node.js de um jeito que funcione tanto no seu laptop quanto num cluster de produção.
Resposta rápida
Para gerenciar variáveis de ambiente em Docker para produção: nunca use .env dentro da imagem, nunca passe secrets via ENV no Dockerfile (eles ficam gravados nas camadas da imagem), use docker run --env-file ou o mecanismo de secrets da sua plataforma de deploy. No código da aplicação, leia process.env e valide os valores na inicialização com um schema, não em runtime.
Principais pontos
.envé para desenvolvimento local. Em produção, ele não deveria existir dentro do container.ENVno Dockerfile define valores de build. Não coloque credentials ali. Qualquer pessoa com acesso à imagem pode inspecionar as camadas comdocker history.- Use
--env-fileou variáveis de ambiente injetadas pela plataforma no momento do deploy. - Valide as env vars na inicialização da aplicação. Se falta uma variável obrigatória, o processo deve falhar na hora, não depois de 200 requests.
- Para Node.js, use um módulo de validação (Zod,
envalid) em vez de espalharprocess.env.X ?? 'default'pelo código.
Quando este guia se aplica
Serve para qualquer aplicação que roda em container Docker e precisa de configuração externa: APIs Node.js, serviços Python com FastAPI, aplicações Go, workers que consomem fila. O padrão é o mesmo: a aplicação lê do ambiente, a infraestrutura injeta os valores.
Se você usa docker-compose em produção (o que eu não recomendo, mas muita gente faz), o guia também aplica. A diferença é que o Compose tem seu próprio mecanismo de env_file e interpolação que adiciona uma camada extra de complexidade.
Quando não usar este padrão
Se a aplicação é serverless (Lambda, Cloud Functions), a forma de passar configuração muda completamente. AWS usa Parameter Store ou Secrets Manager integrado ao event. Google Cloud usa Secret Manager com mount de volume. O conceito é o mesmo (separar código de configuração), mas os mecanismos são diferentes.
Se o projeto usa config files em YAML/JSON (Kubernetes ConfigMaps, por exemplo), o padrão 12-factor ainda vale, mas a injeção acontece via volume mount ou sub-resource do pod, não via --env.
Antes de começar
- Um projeto Node.js (ou qualquer linguagem) que usa variáveis de ambiente
- Docker instalado localmente
- Conta na Guara Cloud para o deploy de produção
1. O problema com .env em imagens Docker
O Dockerfile mais comum que eu vejo em projetos open source:
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"] O COPY . . copia tudo. Incluindo .env. Esse arquivo agora faz parte de uma camada da imagem. Qualquer pessoa que faz docker pull da imagem pode extrair o conteúdo com docker create + docker cp. Os segredos estão na história da imagem para sempre, mesmo que você delete o .env depois e rebuild. As camadas antigas continuam lá.
2. Separe o .env do build
Adicione .env ao .dockerignore:
node_modules
.env
.env.*
.git
Dockerfile
docker-compose*.yml Agora o COPY ignora o arquivo. Mas isso resolve só metade do problema. Você ainda precisa que os valores cheguem ao container em runtime.
3. Injete variáveis no runtime, não no build
O jeito certo com docker run:
docker run --rm \
--env-file .env \
-p 3000:3000 \
minha-app:latest O --env-file lê o arquivo no host e injeta os valores como variáveis de ambiente no container. O .env nunca entra na imagem. Se o arquivo muda, você não precisa rebuildar nada. Só reinicia o container.
Para docker-compose:
services:
api:
image: minha-app:latest
env_file:
- .env
ports:
- "3000:3000" O Compose resolve interpolação de ${VAR} dentro do próprio compose file, o que às vezes causa confusão. Se você tem um .env na raiz do projeto, o Compose lê automaticamente para substituir variáveis no YAML. Isso é separado do env_file que injeta valores no container. Dois mecanismos diferentes, mesmo arquivo. Preste atenção nisso.
4. Valide env vars na inicialização
O erro mais comum que encontro em codebases Node.js: a aplicação lê process.env.DATABASE_URL em uma função qualquer, não valida, e só descobre que a variável estava vazia quando a primeira query falha. Em produção, isso pode significar 4 mil requests com erro antes de alguém olhar os logs.
Use validação no boot:
import { z } from 'zod';
const envSchema = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']),
REDIS_URL: z.string().url().optional(),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('Variáveis de ambiente inválidas:');
console.error(parsed.error.format());
process.exit(1);
}
export const env = parsed.data; Se DATABASE_URL não está definido ou não é uma URL válida, o processo morre com exit code 1 e uma mensagem clara. O orquestrador (Kubernetes, Docker Swarm, a Guara Cloud) tenta reiniciar, falha de novo, e você vê no painel que tem um problema de configuração. Muito melhor do que o container subir “saudável” e falhar silenciosamente em cada request.
5. Secrets no Dockerfile: o que fazer e o que não fazer
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"] Note que não tem ENV DATABASE_URL=... em lugar nenhum. O Dockerfile define a imagem (o código e suas dependências). As variáveis de ambiente com valores sensíveis vêm de fora, no momento em que o container inicia.
Se você precisa de uma variável de build (tipo NEXT_PUBLIC_API_URL para o Next.js), use ARG no Dockerfile e passe com --build-arg:
# ARG: disponível apenas durante o build, não fica na imagem final
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
# ENV: fica gravado na camada da imagem. Use apenas para valores não-sensíveis.
ENV NODE_ENV=production ARG não persiste na imagem. ENV persiste. Para valores públicos de build (URLs de API, feature flags de frontend), ARG + ENV funciona. Para segredos, nem pense nisso.
6. Deploy na Guara Cloud: env vars gerenciadas
Na Guara Cloud, você não precisa de .env file em lugar nenhum. As variáveis são configuradas pelo painel e injetadas no container automaticamente:
Configurar env vars no painel
- Abra o serviço no dashboard da Guara Cloud
- Clique na aba "Variáveis de Ambiente"
- Adicione cada variável (nome e valor)
- Clique em "Salvar e Reiniciar"
- O container reinicia com as novas variáveis injetadas
Exemplo de configuração para API Node.js
| Variável | Valor |
|---|---|
DATABASE_URL | postgresql://user:pass@host:5432/db |
NODE_ENV | production |
LOG_LEVEL | info |
JWT_SECRET | (gerado automaticamente, nunca commite) |
A plataforma separa a variável “DATABASE_URL” do código e da imagem. Se você precisar trocar a string de conexão (migrar para outro banco, por exemplo), muda o valor no painel e o container reinicia. Sem rebuild, sem novo deploy, sem risco de vazar no registry.
Troubleshooting
Problemas comuns
- Problema A aplicação não enxerga a variável de ambiente dentro do container
- Solução Verifique se você está usando --env-file ou --env no docker run. Se usa docker-compose, confirme que o env_file aponta para o caminho certo relativo ao compose file.
- Problema docker-compose resolve ${VAR} e substitui por string vazia
- Solução O Compose tenta substituir ${VAR} no YAML antes de tudo. Se VAR não está definida no host, o resultado é string vazia. Use $$VAR (com dois cifrões) para literal, ou defina a variável no host antes de rodar compose up.
- Problema A variável está no .env mas o container lê o valor antigo
- Solução Se você usa ENV no Dockerfile, ele tem prioridade sobre --env-file apenas se o env-file não define aquela mesma variável. Na verdade, --env-file sobrescreve ENV do Dockerfile. Confirme que não tem um ENV hardcoded no Dockerfile com o mesmo nome.
- Problema Secrets aparecem no docker inspect ou docker history
- Solução Isso acontece quando você usa ENV no Dockerfile para definir secrets. Remova o ENV, rebuild a imagem, e passe as variáveis apenas via --env-file ou pelo painel da plataforma. Para imagens antigas, você precisa rebuild desde o começo (as camadas com secrets não podem ser removidas).
FAQ
Qual a diferença entre ENV no Dockerfile e --env no docker run?
ENV no Dockerfile grava a variável na imagem. Qualquer pessoa com acesso à imagem pode lê-la com docker inspect. --env no docker run injeta a variável apenas naquele container em runtime, sem gravar na imagem. Para produção, use sempre a injeção em runtime.
Posso usar dotenv em Node.js junto com Docker?
Pode, mas não deveria em produção. O pacote dotenv lê um arquivo .env do disco. Se o arquivo não existe (porque está no .dockerignore), dotenv simplesmente não carrega nada e seu código continua dependendo de process.env, que foi injetado pela plataforma. Em produção, configure dotenv com override: false para que variáveis de ambiente reais tenham prioridade.
Como faço rotate de secrets sem downtime?
Em plataformas como a Guara Cloud, você atualiza a variável no painel e o container reinicia com o novo valor. O restart dura alguns segundos. Para zero downtime, use rolling updates com múltiplas réplicas (a plataforma reinicia um pod por vez).
Qual a melhor forma de compartilhar env vars entre desenvolvimento e produção?
Não compartilhe. Use valores diferentes. O .env local tem credenciais de desenvolvimento. A plataforma de produção tem credenciais reais. O que deve ser idêntico entre os ambientes é a lista de nomes das variáveis, não os valores.
Resumindo
Variáveis de ambiente em Docker têm três regras difíceis de errar: nunca coloque secrets na imagem, valide a configuração no boot da aplicação, e deixe a plataforma de deploy cuidar da injeção em produção. Se você seguir isso, elimina 90% dos problemas que vejo em codebases de times que estão migrando para containers.
Gerencie env vars sem dor de cabeça
Na Guara Cloud, variáveis de ambiente ficam seguras no painel e são injetadas automaticamente no container. Sem .env no repositório, sem rebuild quando o valor muda.