Dominando Variáveis de Ambiente no Docker: Configuração vs. Segredos

Desbloqueie implantações Docker seguras e flexíveis dominando variáveis de ambiente. Este guia abrangente esclarece a distinção crítica entre usar variáveis de ambiente para configuração geral de aplicações e gerenciar de forma segura dados sensíveis como chaves de API e senhas. Aprenda métodos práticos para passar configurações não sensíveis, entenda os graves riscos de expor segredos por meio de variáveis de ambiente e descubra como aproveitar o Docker Secrets e o Compose para um gerenciamento robusto e criptografado de segredos. Eleve seu conhecimento em Docker e proteja suas aplicações.

Dominando Variáveis de Ambiente no Docker: Configuração vs. Segredos

Variáveis de ambiente são convenientes no Docker porque permitem que a mesma imagem seja executada em desenvolvimento, homologação e produção com configurações diferentes. Essa conveniência se torna arriscada quando equipes colocam senhas, chaves de assinatura e tokens de API no mesmo balde que níveis de log e números de porta.

O modelo mental limpo é simples: variáveis de ambiente são adequadas para configuração de tempo de execução não sensível. Segredos devem vir de um armazenamento de segredos ou arquivo de segredos montado, com acesso limitado e um plano de rotação.

Entendendo Variáveis de Ambiente para Configuração

Variáveis de ambiente são um método direto e amplamente adotado para passar configuração de tempo de execução para aplicações, incluindo aquelas executadas em contêineres Docker. Elas permitem modificar o comportamento de uma aplicação sem reconstruir a imagem Docker, tornando seus contêineres mais flexíveis e portáteis. Isso é ideal para configurações dinâmicas e não sensíveis, como números de porta da aplicação, flags de depuração ou URLs de serviços de terceiros.

Métodos para Passar Variáveis de Configuração

O Docker fornece várias maneiras de definir e injetar variáveis de ambiente em seus contêineres:

1. Instrução ENV no Dockerfile

A instrução ENV define uma variável de ambiente padrão que estará disponível dentro do contêiner quando ele for executado. Isso é adequado para variáveis que provavelmente não mudarão ou fornecem padrões sensatos para sua aplicação.

FROM alpine:latest

ENV APP_PORT=8080
ENV DEBUG_MODE=false

COPY ./app /app
WORKDIR /app
CMD ["/app/start.sh"]

Dica: Embora ENV defina padrões, eles podem ser substituídos em tempo de execução.

2. Flag -e ou --env com docker run

Ao iniciar um único contêiner, você pode usar a flag -e ou --env para passar variáveis de ambiente diretamente. Isso é comum para testes ad-hoc ou para fornecer configurações específicas que diferem dos padrões do Dockerfile.

docker run -d -p 80:8080 --name my_app_instance \
  -e APP_PORT=80 \
  -e DEBUG_MODE=true \
  my_app_image:latest

3. env_file no Docker Compose

Para gerenciar múltiplas variáveis de ambiente, especialmente em vários serviços definidos em um arquivo docker-compose.yml, a opção env_file é muito conveniente. Ela permite carregar variáveis de um ou mais arquivos .env, mantendo seu docker-compose.yml mais limpo.

docker-compose.yml:

version: '3.8'
services:
  webapp:
    image: my_app_image:latest
    ports:
      - "80:8080"
    env_file:
      - ./config/app.env

./config/app.env:

APP_PORT=8080
DEBUG_MODE=false
API_ENDPOINT=https://api.example.com/v1

4. Chave environment no Docker Compose

Alternativamente, você pode definir variáveis de ambiente diretamente na seção environment de um serviço no docker-compose.yml. Isso é frequentemente preferido para um pequeno número de variáveis ou para variáveis específicas de um único serviço.

version: '3.8'
services:
  webapp:
    image: my_app_image:latest
    ports:
      - "80:8080"
    environment:
      APP_PORT: 8080
      DEBUG_MODE: false

As Armadilhas de Usar Variáveis de Ambiente para Segredos

Embora variáveis de ambiente sejam excelentes para configuração, elas são fundamentalmente não seguras para gerenciar dados sensíveis (segredos) como senhas de banco de dados, chaves de API ou chaves SSH privadas. Esta é uma vulnerabilidade de segurança crítica que muitas vezes é negligenciada, especialmente em ambientes de desenvolvimento.

Por que Variáveis de Ambiente são Inseguras para Segredos:

  1. Visibilidade via docker inspect: Qualquer pessoa com acesso ao host Docker pode facilmente visualizar as variáveis de ambiente de um contêiner em execução usando docker inspect <container_id>. Isso significa que seus segredos são visíveis em texto simples.

    # Exemplo de exposição de um segredo (NÃO FAÇA ISSO EM PRODUÇÃO)
    docker run -d -e DB_PASSWORD=mysecretpassword --name insecure_app nginx:latest
    
    # Qualquer um pode ver a senha
    docker inspect insecure_app | grep DB_PASSWORD
    
  2. Espionagem de Processos: Dentro do contêiner, outros processos ou usuários (se houver vários usuários) podem ser capazes de ler variáveis de ambiente, especialmente se a aplicação for executada como root ou tiver privilégios elevados.

  3. Registros e Histórico: Variáveis de ambiente podem inadvertidamente acabar em logs, histórico de pipeline CI/CD ou histórico do shell, levando à exposição acidental.

  4. Camadas de Imagem: Se você usar ENV em um Dockerfile com um segredo, esse segredo é incorporado em uma camada de imagem e permanece lá, mesmo se você tentar unset em uma camada posterior. Isso torna o segredo recuperável da própria imagem.

  5. Compartilhamento Acidental: Arquivos .env ou docker-compose.yml contendo segredos são frequentemente commitados em sistemas de controle de versão ou compartilhados inadequadamente, levando à exposição generalizada.

Aviso: Tratar informações sensíveis como variáveis de ambiente regulares é um erro de segurança comum. Sempre assuma que variáveis de ambiente são publicamente visíveis no host e dentro do contêiner.

Gerenciando Segredos de Forma Segura no Docker

Para lidar com as deficiências de segurança das variáveis de ambiente para dados sensíveis, o Docker fornece capacidades dedicadas de gerenciamento de segredos, principalmente através do Docker Secrets (para Docker Swarm) e ferramentas externas como o Docker Compose com funcionalidade secrets (que pode aproveitar os segredos do Docker Swarm ou simplesmente montar arquivos).

Docker Secrets (Modo Docker Swarm)

Docker Secrets é um recurso integrado ao modo Docker Swarm que fornece uma maneira segura de transmitir e armazenar dados sensíveis para serviços. Os segredos são:

  • Criptografados em repouso nos logs Raft do gerenciador Swarm.
  • Transmitidos de forma segura para tarefas de serviço autorizadas.
  • Montados como arquivos em memória no sistema de arquivos do contêiner, tipicamente em /run/secrets/<nome_do_segredo>, em vez de expostos como variáveis de ambiente.
  • Acessíveis apenas por serviços explicitamente autorizados.

Como Usar Docker Secrets (Modo Swarm)

  1. Inicialize o Swarm (se ainda não o fez):
    
    

docker swarm init ```

  1. Crie um Segredo: Segredos são criados a partir de um arquivo ou entrada padrão.
    
    

echo "my_secure_db_password" | docker secret create db_password_secret - echo "SG.your_api_key_here" | docker secret create sendgrid_api_key - ```

  1. Implante um Serviço com o Segredo: Os serviços referenciam segredos pelo nome. O Docker monta o segredo no contêiner.
    
    

docker service create --name my-webapp
--secret db_password_secret
--secret sendgrid_api_key
my_app_image:latest ```

  1. Acessando Segredos no Contêiner: As aplicações leem o segredo do caminho do arquivo montado.
    
    

No código da sua aplicação Python (ou similar para outras linguagens)

with open('/run/secrets/db_password_secret', 'r') as f: db_password = f.read().strip()

with open('/run/secrets/sendgrid_api_key', 'r') as f: sendgrid_key = f.read().strip() ```

Docker Compose e Segredos (para host único ou Swarm)

O Docker Compose versão 3.1+ introduziu uma seção secrets, que permite definir e referenciar segredos dentro do seu docker-compose.yml. Ao executar no modo Swarm, o Compose aproveita os segredos nativos do Docker Swarm. Ao executar em um único host sem o modo Swarm, o Compose ainda suporta segredos montando arquivos do host no contêiner de forma segura, embora sem a criptografia em repouso fornecida pelo Swarm.

Usando secrets no docker-compose.yml

  1. Defina Segredos: Você pode definir segredos referenciando um arquivo externo ou tornando-o um segredo externo (segredo Swarm pré-criado).

    # docker-compose.yml
    version: '3.8'
    
    services:
      webapp:
        image: my_app_image:latest
        ports:
          - "80:8080"
        secrets:
          - db_password
          - sendgrid_api_key
    
    secrets:
      db_password:
        file: ./secrets/db_password.txt # Caminho para um arquivo no host contendo a senha
      sendgrid_api_key:
        external: true                   # Refere-se a um segredo Docker Swarm pré-existente chamado 'sendgrid_api_key'
    
  2. Crie Arquivos de Segredo Locais (se file for usado):

    
    

mkdir secrets echo "my_local_db_password" > ./secrets/db_password.txt ```

  1. Implante com Compose: docker compose up -d implantará seus serviços, disponibilizando segredos em /run/secrets/<nome_do_segredo> dentro dos contêineres.

    # Dentro do contêiner, o conteúdo de ./secrets/db_password.txt estará em:
    # /run/secrets/db_password
    

Escolhendo a Ferramenta Certa: Configuração vs. Segredos

A decisão de usar uma variável de ambiente para configuração ou uma solução dedicada de gerenciamento de segredos se resume a uma pergunta principal:

Os dados são sensíveis?

  • Se Sim (dados sensíveis): Use Docker Secrets (com Swarm) ou um sistema de gerenciamento de segredos similar (ex.: Kubernetes Secrets, HashiCorp Vault). Para configurações Compose de host único, use a seção secrets para montar arquivos de forma segura.
  • Se Não (configuração não sensível): Use variáveis de ambiente (via ENV no Dockerfile, flag -e, env_file ou environment no Compose).
Recurso Variáveis de Ambiente (para config) Docker Secrets (para dados sensíveis)
Propósito Configuração de aplicação não sensível Dados sensíveis como senhas e chaves de API
Visibilidade Visível via docker inspect e frequentemente inspeção de processos Montado como arquivos; não mostrado como valores de ambiente normais
Segurança Não apropriado para dados sensíveis Manuseio mais forte; segredos Swarm são criptografados em repouso, enquanto segredos Compose locais dependem da proteção do arquivo host
Acesso no App Lido de os.environ ou similar Lido do arquivo /run/secrets/<nome_do_segredo>
Gerenciado por Docker runtime, Docker Compose Docker Swarm, Docker Compose, ou um gerenciador de segredos externo
Casos de Uso Números de porta, flags de depuração, URLs não sensíveis Senhas de banco de dados, tokens de API, chaves privadas

Melhores Práticas para Ambos

Para Configuração (Variáveis de Ambiente):

  • Forneça padrões sensatos no seu Dockerfile usando ENV. Isso torna suas imagens executáveis imediatamente e documenta claramente as variáveis esperadas.
  • Externalize a configuração quando possível. Use arquivos .env com docker compose ou serviços de configuração externos para implantações maiores.
  • Documente todas as opções de configuração e seus valores esperados, talvez em um README.md ou documentação da aplicação.
  • Evite codificar valores que possam mudar entre ambientes (desenvolvimento, homologação, produção).

Para Segredos (Docker Secrets e além):

  • Nunca commite segredos (ex.: arquivos .env contendo segredos, db_password.txt) em sistemas de controle de versão como Git.
  • Rotacione segredos regularmente. Isso minimiza a janela de exposição se um segredo for comprometido.
  • Conceda privilégio mínimo. Dê aos serviços acesso apenas aos segredos que eles absolutamente precisam.
  • Evite registrar valores de segredos. Certifique-se de que os logs da sua aplicação e infraestrutura não imprimam o conteúdo dos segredos.
  • Para implantações de nível empresarial em grande escala, considere soluções dedicadas de gerenciamento de segredos como HashiCorp Vault, AWS Secrets Manager ou Azure Key Vault, que oferecem recursos mais avançados como auditoria, geração dinâmica de segredos e integração com Identity and Access Management (IAM).

Uma Regra Prática para Decidir Onde Colocar

Antes de adicionar um valor a environment, pergunte o que aconteceria se um colega de equipe o colasse em um ticket de suporte. Se a resposta for "nada sério", provavelmente é configuração. Se a resposta for "nós rotacionaríamos as credenciais", é um segredo.

Boas variáveis de ambiente:

APP_ENV=production
LOG_LEVEL=info
PUBLIC_BASE_URL=https://example.com
FEATURE_SIGNUP_ENABLED=false
REDIS_HOST=redis

Variáveis de ambiente ruins:

DATABASE_PASSWORD=...
STRIPE_SECRET_KEY=...
JWT_SIGNING_KEY=...
AWS_SECRET_ACCESS_KEY=...
PRIVATE_SSH_KEY=...

Existem áreas cinzentas. Um nome de host de banco de dados é geralmente configuração. Uma URL de banco de dados completa que inclui nome de usuário e senha é um segredo. Uma chave de análise pública pode ser segura para uma aplicação de navegador, enquanto um token de API privado para o mesmo fornecedor não é. Em caso de dúvida, trate o valor como sensível até que prove o contrário.

Arquivos .env do Compose São Fáceis de Entender Errado

O Docker Compose usa .env de duas maneiras diferentes que as pessoas frequentemente confundem.

Primeiro, o Compose lê um arquivo .env de nível de projeto para substituição de variáveis dentro do compose.yml:

services:
  web:
    image: "${APP_IMAGE}"
    ports:
      - "${HOST_PORT}:8080"

Segundo, env_file passa variáveis para dentro do contêiner:

services:
  web:
    image: my-app
    env_file:
      - ./app.env

Esses arquivos podem parecer semelhantes, mas servem a propósitos diferentes. O primeiro ajuda o Compose a renderizar a configuração. O segundo se torna ambiente de tempo de execução dentro do contêiner. Não assuma que um valor no .env do projeto aparece automaticamente dentro do contêiner a menos que você o passe explicitamente.

Para desenvolvimento local, um arquivo de exemplo verificado é útil:

# .env.example
APP_ENV=development
LOG_LEVEL=debug
PUBLIC_BASE_URL=http://localhost:3000

Em seguida, mantenha o .env real fora do Git:

.env
*.env.local
secrets/

O arquivo de exemplo documenta o que o aplicativo espera sem expor valores privados.

Lendo Segredos Baseados em Arquivo em uma Aplicação

Muitas aplicações já esperam segredos em variáveis de ambiente. A transição para segredos baseados em arquivo é mais fácil se você suportar ambos os padrões por um tempo.

Por exemplo, um helper Node.js:

import fs from "node:fs";

function readSecret(name) {
  const filePath = process.env[`${name}_FILE`];
  if (filePath) {
    return fs.readFileSync(filePath, "utf8").trim();
  }
  return process.env[name];
}

const databasePassword = readSecret("DATABASE_PASSWORD");

Então seu arquivo Compose pode apontar para um arquivo de segredo montado:

services:
  web:
    image: my-app
    environment:
      DATABASE_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Este padrão funciona bem porque a aplicação ainda pode ser executada em ambientes mais antigos enquanto você move a produção para segredos montados em arquivo. Muitas imagens oficiais já suportam variáveis terminadas em _FILE por esta razão.

Não Coloque Segredos em Argumentos de Build Também

Variáveis de ambiente não são a única armadilha. Argumentos de build também podem vazar se você os usar para buscar pacotes privados ou clonar repositórios:

ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN

Mesmo que o contêiner final não mostre NPM_TOKEN, o histórico de build e camadas intermediárias podem expor mais do que você espera. Com o BuildKit, use montagens de segredo para segredos em tempo de build:

# syntax=docker/dockerfile:1.7
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npm_token \
  NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci

Construa assim:

docker build \
  --secret id=npm_token,src=.npm-token \
  -t my-app .

Isso mantém o token fora do Dockerfile e evita incorporá-lo em uma camada normal. Você ainda precisa proteger o arquivo .npm-token local e o armazenamento de segredos do CI.

Kubernetes, Gerenciadores de Segredos na Nuvem e Docker

Docker Secrets são úteis no Swarm, e segredos do Compose são úteis para configurações locais ou de host único. No Kubernetes, você normalmente usará Kubernetes Secrets, um operador de segredos externo ou uma integração com gerenciador de segredos na nuvem. Na AWS, as equipes costumam usar o AWS Secrets Manager ou o Systems Manager Parameter Store. No Azure, o Azure Key Vault é comum. No Google Cloud, o Secret Manager preenche o mesmo papel.

O princípio é o mesmo em todas as plataformas:

  • Armazene valores sensíveis em um sistema projetado para segredos.
  • Conceda à identidade de tempo de execução acesso apenas aos segredos de que precisa.
  • Monte ou injete segredos em tempo de execução.
  • Rotacione segredos sem reconstruir a imagem.
  • Mantenha segredos fora do controle de origem, camadas de imagem, logs e painéis.

Kubernetes Secrets são codificados por padrão, não automaticamente criptografados em todas as configurações de cluster. Muitos clusters gerenciados suportam criptografia em repouso, mas verifique as configurações reais do cluster em vez de assumir. Para credenciais de alto risco, use um gerenciador de segredos na nuvem ou uma ferramenta dedicada com logs de auditoria e suporte a rotação.

Rotação é Parte do Design

Uma estratégia de segredos que não pode ser rotacionada está inacabada. Faça estas perguntas antes da produção:

  • Podemos alterar a senha do banco de dados sem reconstruir a imagem?
  • Duas credenciais válidas podem se sobrepor durante uma implantação?
  • A aplicação relê segredos ou precisa de uma reinicialização?
  • Onde as credenciais antigas são registradas, armazenadas em cache ou armazenadas?
  • Quem é notificado quando um segredo muda?

Para bancos de dados, a rotação geralmente significa criar uma segunda credencial, implantar a aplicação com a nova credencial, verificar o tráfego e depois revogar a antiga. Para chaves de API, depende do provedor. Alguns serviços permitem várias chaves ativas; outros forçam uma migração. Projete seu processo de implantação em torno da dependência menos flexível.

Limpe a Exposição Acidental

Se um segredo já foi commitado no Git ou incorporado em uma imagem, deletar a linha não é suficiente. Trate-o como exposto.

A resposta usual é:

  1. Revogue ou rotacione a credencial.
  2. Remova-a do código ou imagem atual.
  3. Verifique logs de CI, registros de imagem, rastreadores de problemas e mensagens de chat em busca de cópias.
  4. Reescreva o histórico do Git apenas se sua organização estiver preparada para lidar com a coordenação; a rotação ainda é necessária.
  5. Adicione varredura ou verificações de pré-commit para reduzir erros repetidos.

Ferramentas podem ajudar, mas não substituem hábitos. Mantenha arquivos de segredo nomeados claramente, ignore-os no Git e evite imprimir objetos de configuração por completo na inicialização.

O Padrão de Trabalho

Use variáveis de ambiente para valores que descrevem como o aplicativo deve ser executado neste ambiente: portas, níveis de log, flags de recurso, nomes de host de serviço e URLs não sensíveis. Use segredos para valores que provam identidade ou concedem acesso: senhas, tokens, chaves de assinatura, chaves privadas e credenciais de provedor.

A imagem Docker limpa é a mesma em todos os ambientes. Desenvolvimento, homologação e produção mudam o comportamento em tempo de execução. A configuração pode viajar como variáveis de ambiente. Os segredos devem vir de um armazenamento de segredos ou arquivo de segredos montado com acesso limitado. Essa separação mantém as implantações flexíveis sem transformar cada inspeção de contêiner, linha de log ou camada de imagem em um vazamento de credenciais.