Secrets em produção: Doppler e HashiCorp Vault

Hardcoded secrets em código e .env no servidor são os vetores de vazamento mais comuns. Gerenciadores de secrets como Doppler e HashiCorp Vault centralizam o controle, criptografam em repouso, auditam cada acesso e permitem rotação sem downtime, eliminando a dependência de arquivos .env espalhados por servidores.

Doppler: secrets como variáveis de ambiente

Injetar secrets automaticamente sem arquivos .env:

bash
# Instalar Doppler CLI no VPS:
(curl -Ls --tlsv1.2 --proto "=https" -o install.sh   https://cli.doppler.com/install.sh) && sudo sh install.sh

# Autenticar (em desenvolvimento, usa browser; em CI/CD, usa service token):
doppler login

# Configurar projeto no VPS:
doppler setup --project minha-api --config prd

# Executar aplicação com secrets injetados como env vars:
doppler run -- node server.js
# ou:
doppler run -- npm start

# Verificar quais secrets serão injetados:
doppler secrets --only-names

# Configurar como serviço systemd que injeta secrets:
sudo tee /etc/systemd/system/minha-api.service << 'EOF'
[Unit]
Description=Minha API Node.js
After=network.target

[Service]
User=nodejs
WorkingDirectory=/app
ExecStart=/usr/local/bin/doppler run -- node server.js
Restart=always
RestartSec=5

# Token do Doppler para autenticação no servidor:
Environment=DOPPLER_TOKEN=dp.st.prd.xxxxxxxxxxxxxxxxxxxx

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable minha-api

HashiCorp Vault: gerenciamento enterprise de secrets

Instalar e configurar Vault para gerenciamento avançado:

bash
# Instalar HashiCorp Vault no Ubuntu:
wget -O- https://apt.releases.hashicorp.com/gpg |   sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg]   https://apt.releases.hashicorp.com $(lsb_release -cs) main" |   sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault

# Configuração básica do Vault:
sudo tee /etc/vault.d/vault.hcl << 'EOF'
ui = true

storage "file" {
  path = "/opt/vault/data"
}

listener "tcp" {
  address     = "127.0.0.1:8200"
  tls_disable = "true"   # Em produção: usar TLS
}

api_addr = "http://127.0.0.1:8200"
EOF

sudo systemctl enable vault && sudo systemctl start vault

# Inicializar o Vault (primeira vez):
export VAULT_ADDR='http://127.0.0.1:8200'
vault operator init -key-shares=3 -key-threshold=2
# Salva as 3 unseal keys e o root token em local SEGURO

# Desselar (necessário após cada restart):
vault operator unseal UNSEAL_KEY_1
vault operator unseal UNSEAL_KEY_2

Armazenar e ler secrets no Vault

Usar o KV secrets engine para armazenar credenciais:

bash
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='s.xxxxxxxxxxxxxxxxxxxxxxxx'  # root token

# Habilitar KV secrets engine versão 2:
vault secrets enable -path=minha-api kv-v2

# Armazenar secrets:
vault kv put minha-api/producao   DATABASE_URL="postgresql://user:senha@localhost/db"   JWT_SECRET="$(openssl rand -base64 32)"   REDIS_URL="redis://localhost:6379"   STRIPE_SECRET_KEY="sk_live_xxxxxxxxx"

# Ler secrets:
vault kv get minha-api/producao
vault kv get -field=DATABASE_URL minha-api/producao

# Versionar secrets (KV v2 mantém histórico):
vault kv put minha-api/producao JWT_SECRET="novo-secret"
vault kv get -version=1 minha-api/producao   # versão anterior

# Criar AppRole para autenticação da aplicação (sem root token):
vault auth enable approle

vault policy write minha-api-policy - << 'EOF'
path "minha-api/data/producao" {
  capabilities = ["read"]
}
EOF

vault write auth/approle/role/minha-api   token_policies="minha-api-policy"   token_ttl=1h   token_max_ttl=4h

# Obter credentials da aplicação:
vault read auth/approle/role/minha-api/role-id
vault write -f auth/approle/role/minha-api/secret-id

Ler secrets do Vault na aplicação Node.js

Integrar Vault com aplicação Node.js para injetar secrets em startup:

typescript
// npm install node-vault

import vault from 'node-vault'

async function carregarSecrets(): Promise<Record<string, string>> {
  const client = vault({
    apiVersion: 'v1',
    endpoint: process.env.VAULT_ADDR || 'http://127.0.0.1:8200',
  })

  // Autenticar via AppRole:
  const auth = await client.approleLogin({
    role_id: process.env.VAULT_ROLE_ID!,
    secret_id: process.env.VAULT_SECRET_ID!,
  })
  client.token = auth.auth.client_token

  // Ler secrets:
  const { data } = await client.read('minha-api/data/producao')
  return data.data
}

// Inicialização da aplicação:
async function main() {
  const secrets = await carregarSecrets()

  process.env.DATABASE_URL = secrets.DATABASE_URL
  process.env.JWT_SECRET = secrets.JWT_SECRET
  process.env.REDIS_URL = secrets.REDIS_URL

  // Iniciar a aplicação após injetar os secrets:
  const app = await criarApp()
  app.listen(3000)
}

// Rotação de secrets sem restart (Vault lease renew):
// O token tem TTL de 1h — renovar antes de expirar:
setInterval(async () => {
  try {
    await client.tokenRenewSelf()
  } catch {
    // Reutenticar se o token não puder ser renovado:
    const secrets = await carregarSecrets()
    atualizarSecrets(secrets)
  }
}, 30 * 60 * 1000)  // renovar a cada 30 minutos

Boas práticas de segurança para .env em produção

Alternativas simples quando Vault/Doppler são excesso para o projeto:

bash
# Prática mínima se .env for necessário:

# 1. Nunca commitar .env no git:
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
echo "*.env" >> .gitignore

# 2. Permissões restritas no arquivo:
chmod 600 /app/.env           # apenas o dono pode ler/escrever
chown nodejs:nodejs /app/.env # dono = usuário da aplicação

# 3. .env fora do diretório da aplicação:
# /etc/minha-api/env  (não dentro de /app que pode ter outros arquivos)
chmod 640 /etc/minha-api/env
chown root:nodejs /etc/minha-api/env

# 4. Usar systemd EnvironmentFile para carregar sem .env no diretório da app:
# /etc/systemd/system/minha-api.service:
# [Service]
# EnvironmentFile=/etc/minha-api/env
# ExecStart=/usr/bin/node /app/server.js

# 5. Verificar se secrets não estão em processo ou logs:
ps aux | grep node  # não deve mostrar DATABASE_URL=... como argumento
# Logs não devem logar objetos req (que podem conter Authorization headers)

# 6. Rotação: quando rotacionar uma credencial:
# Atualizar .env → reload da aplicação sem downtime:
sudo systemctl reload minha-api  # envia SIGHUP, mais suave que restart

Não quer configurar manualmente?

Implante o VPS Linux em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

.env com permissões corretas: projetos pessoais, MVPs, aplicações com poucos segredos e sem auditoria. Doppler: SaaS de gestão de secrets, ótimo custo-benefício para startups e times pequenos, fácil integração com CI/CD e múltiplos ambientes (dev/staging/prod), dashboard web para não-técnicos. Vault: controle total self-hosted, secrets dinâmicos (credenciais geradas sob demanda), compliance formal, múltiplos backends de storage, PKI. Para maioria dos VPS de produção: Doppler é o equilíbrio ideal.

GitHub Secrets são criptografados em repouso e injetados como variáveis de ambiente nos workflows, nunca aparecem em logs (mascarados automaticamente). Seguro para: deploy keys SSH, tokens de API de terceiros, credenciais de registry. Limitações: não têm auditoria granular de quem usou cada secret, não suportam rotação automática, todos os admins do repositório podem ver/usar. Para maior controle: use Doppler ou Vault com integração OIDC do GitHub Actions (sem segredo estático no GitHub).

Estratégia de dois estágios: (1) adicionar o novo secret ao lado do antigo (a aplicação aceita os dois); (2) atualizar os sistemas que usam o secret para o novo; (3) remover o antigo. Para JWT: suportar múltiplos secrets na verificação por um período de transição. Para banco de dados: criar novo usuário com nova senha, atualizar a aplicação, remover o antigo. Doppler e Vault têm versioning nativo, facilita o rollback se algo der errado.

Depende da estratégia de integração. Se a aplicação lê do Vault no startup e armazena em memória: o Vault precisa estar disponível apenas durante o startup. Se a aplicação consulta o Vault a cada request: alta disponibilidade do Vault é crítica. Para produção com Vault: configure clustering com 3+ nós (Raft storage) ou use Vault Cloud (HCP Vault). Alternativa simples: ler secrets no startup, armazenar em memória, ter fallback para .env se o Vault estiver indisponível.

Vazamento em logs é o mais comum: console.log(config) ou logger.info({ req }) pode imprimir DATABASE_URL com senha. Seguido de: secrets em variáveis de ambiente visíveis via /proc/PID/environ (todos os processos no mesmo host podem ler); secrets em imagens Docker (RUN export SECRET=x deixa no layer history); secrets em arquivos de configuração com permissões abertas (chmod 644 em vez de 600). Auditoria: ferramentas como git-secrets, truffleHog e Semgrep detectam secrets em código antes do commit.