Variáveis de ambiente em Node.js: .env e segurança

    Variáveis de ambiente são o mecanismo padrão para separar configuração de código — conexão ao banco, chaves de API e segredos nunca devem estar no código-fonte. Em Node.js, o dotenv carrega o arquivo .env em desenvolvimento; em produção na VPS, as variáveis vêm do docker-compose.yml ou do sistema operacional.

    dotenv: carregando .env em desenvolvimento

    dotenv é o pacote padrão para carregar variáveis de ambiente de um arquivo .env:

    typescript
    # Instalar dotenv
    npm install dotenv
    
    # Carregar o mais cedo possível (primeiro import da aplicação)
    # src/index.ts ou src/index.js
    import 'dotenv/config'    // ESM
    // ou
    require('dotenv').config() // CJS
    
    // .env (nunca commitado no git)
    DATABASE_URL=postgresql://user:pass@localhost:5432/db
    JWT_SECRET=minha_chave_secreta_muito_longa_e_aleatória
    PORT=3000
    NODE_ENV=development
    
    # .env.example (commitado — documenta as variáveis necessárias)
    DATABASE_URL=
    JWT_SECRET=
    PORT=3000
    NODE_ENV=development
    Dica
    NUNCA commite o arquivo .env. Adicione-o ao .gitignore. Commite apenas .env.example com os nomes das variáveis e valores de exemplo (sem dados reais).

    Validar variáveis de ambiente com Zod

    Falhar no startup por variável ausente é melhor que falhar em runtime com erro críptico. Zod valida e tipifica todas as variáveis na inicialização:

    typescript
    // src/env.ts
    import { z } from 'zod'
    
    const envSchema = z.object({
      NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
      PORT: z.coerce.number().default(3000),
      DATABASE_URL: z.string().url(),
      JWT_SECRET: z.string().min(32, 'JWT_SECRET deve ter ao menos 32 caracteres'),
      REDIS_URL: z.string().url().optional(),
      LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
    })
    
    // Validar imediatamente — lança erro no startup se inválido
    export const env = envSchema.parse(process.env)
    
    // Uso:
    import { env } from './env'
    console.log(env.PORT)         // number (tipado)
    console.log(env.DATABASE_URL) // string (garantida válida)
    Dica
    z.coerce.number() converte a string da variável de ambiente para number automaticamente. Variáveis de ambiente são sempre strings — sem coerce, PORT seria uma string "3000" e server.listen(env.PORT) falharia silenciosamente.

    Variáveis de ambiente em produção (Docker)

    Em produção com Docker, as variáveis vêm do docker-compose.yml ou de um arquivo .env do Compose — nunca de um .env dentro da imagem:

    yaml
    # docker-compose.yml
    services:
      app:
        image: minha-api:latest
        env_file:
          - .env.production    # arquivo .env no host (não na imagem)
        environment:
          NODE_ENV: production  # override de variável específica
    
    # .env.production (no host da VPS, não commitado)
    DATABASE_URL=postgresql://user:pass@postgres:5432/db
    JWT_SECRET=chave_longa_gerada_com_openssl_rand_hex_32
    PORT=3000
    
    # Verificar variáveis resolvidas antes de subir
    docker compose config | grep -A5 "environment:"
    
    # Injetar variável sem arquivo (útil em CI/CD)
    JWT_SECRET=abc123 docker compose up -d
    Atenção
    Nunca copie o arquivo .env para dentro da imagem Docker com COPY .env . no Dockerfile. A imagem pode ser inspecionada (docker inspect, docker history) e os segredos ficariam expostos. Sempre injete variáveis em runtime via env_file ou environment no docker-compose.

    Gerar segredos seguros

    Chaves JWT, session secrets e API keys devem ser geradas de forma criptograficamente segura:

    bash
    # Gerar chave aleatória de 64 caracteres hex (128 bits)
    openssl rand -hex 32
    
    # Para JWT secret (256 bits)
    openssl rand -base64 64
    
    # No Node.js
    node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
    
    # Para UUID como secret (não ideal mas funcional)
    node -e "console.log(require('crypto').randomUUID())"
    
    # Exemplo de .env com chaves geradas:
    JWT_SECRET=a3f8b2c9d4e7f1a0b3c6d9e2f5a8b1c4d7e0f3a6b9c2d5e8f1a4b7c0d3e6f9a2
    SESSION_SECRET=K+5J8mN2pQ7rT0vX3yB6eH9kL1nP4sV7wY0zC3fG6hJ9mO2qR5uW8xA1dF4iI7lN

    Diferentes ambientes: dev, staging, produção

    Organize variáveis por ambiente com arquivos separados:

    typescript
    # Estrutura de arquivos de ambiente
    .env                  # desenvolvimento local (não commitado)
    .env.example          # template (commitado — sem valores)
    .env.test             # testes automatizados (pode ter valores fake)
    
    # .gitignore — garantir que .env nunca seja commitado
    .env
    .env.production
    .env.staging
    *.local
    
    # Carregar ambiente específico com dotenv
    import dotenv from 'dotenv'
    
    const envFile = process.env.ENV_FILE || '.env'
    dotenv.config({ path: envFile })
    
    # Iniciar com env específico:
    ENV_FILE=.env.staging node dist/index.js
    
    # Com docker-compose.override.yml para desenvolvimento
    # (sobrescreve configurações de docker-compose.yml)
    # docker-compose.override.yml:
    # services:
    #   app:
    #     volumes:
    #       - .:/app
    #     command: npm run dev

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes