Docker Compose: .env, env_file e environment
Entenda a diferença entre .env, env_file e environment no Docker Compose, a precedência entre valores e por que senhas não devem ficar em variáveis do contentor.
Os três mecanismos que as pessoas chamam de ficheiro de ambiente
O Docker Compose tem três mecanismos distintos com nomes muito semelhantes. O ficheiro .env preenche os marcadores ${VARIABLE} dentro do próprio compose.yaml, depois de o Compose ler o YAML e antes de validar ou executar qualquer ação. O atributo env_file: carrega um ficheiro de pares chave/valor para o ambiente do contentor. O atributo environment: define variáveis diretamente no contentor, no ficheiro Compose. Estes mecanismos não são intercambiáveis. Quando dois deles definem a mesma chave, o resultado é determinado por uma ordem de precedência documentada.
Este guia mostra cada mecanismo em funcionamento e comprova a precedência com um comando que pode executar. Depois, aborda a parte mais importante: as variáveis de ambiente podem ser lidas por qualquer pessoa que consiga executar docker inspect. Por isso, as palavras-passe não devem ser guardadas nelas. Se ainda não conhece os ficheiros Compose em geral, comece por Conceitos básicos do Docker Compose num VPS e volte aqui para consultar esta explicação.
O ficheiro .env é usado pelo ficheiro Compose, não pelo contentor
Crie um diretório e coloque nele dois ficheiros.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGAgora peça ao Compose para mostrar o que interpretou efetivamente.
docker compose configO resultado mostra image: alpine:3.20. O marcador foi removido porque a interpolação ocorreu quando o Compose carregou o ficheiro. O Compose procura .env no diretório do projeto: o diretório indicado com --project-directory, se o definir; caso contrário, o diretório do primeiro ficheiro passado com -f; caso contrário, o diretório atual. Substitui os marcadores ${NAME} que encontra nos valores YAML. As chaves nunca são interpoladas, e $$ duplicado produz um cifrão literal em vez de uma substituição.
Agora inicie o serviço.
docker compose run --rm demoprintenv ALPINE_TAG termina com o estado 1 e não apresenta qualquer saída. A variável não existe dentro do contentor. Este é o erro de interpretação mais comum: .env configurou o ficheiro Compose, não o processo. Um ficheiro .env que contenha POSTGRES_PASSWORD=hunter2 não tem qualquer efeito na base de dados, a menos que alguma parte do ficheiro Compose o referencie.
${NAME:-default} fornece um valor alternativo quando a variável não está definida ou está vazia. ${NAME:?message} faz o Compose recusar o arranque e apresentar a sua mensagem. Esta é a escolha correta para um valor que não tem um valor predefinido seguro.
env_file carrega variáveis no contentor
O atributo env_file: indica um ou mais ficheiros cujo conteúdo é carregado como variáveis de ambiente do contentor.
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoIsto imprime from_env_file. O formato do ficheiro usa linhas simples de KEY=value, uma por linha, e # no início indica um comentário. Não é um script de shell, embora o Compose analise o ficheiro com algumas regras semelhantes às do shell. As aspas envolventes são removidas, por isso KEY="value" e KEY=value produzem o mesmo resultado. Os valores entre aspas duplas e sem aspas têm os marcadores ${...} interpolados, enquanto os valores entre aspas simples são usados literalmente. O prefixo export é aceite, mas não é necessário. Os espaços em redor do sinal = são removidos. Tenha atenção aos comentários na mesma linha: num valor sem aspas, um espaço seguido de # inicia um comentário. Por isso, coloque entre aspas qualquer valor que contenha #.
Um caminho env_file inexistente causa um erro e o Compose para. Marque o ficheiro como opcional se ele puder legitimamente não existir:
env_file:
- path: ./app.env
required: falseenvironment define variáveis inline
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentSão aceites duas sintaxes: o formato de mapeamento acima e um formato de lista que usa - GREETING=from_environment. O comportamento é idêntico. Ambas têm um recurso adicional: uma chave sem valor transfere a variável do shell onde executou docker compose. No formato de lista, trata-se de um - GREETING isolado. No formato de mapeamento, é GREETING: sem nada depois dos dois-pontos.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoIsto imprime from_my_shell. Execute o comando sem definir GREETING no shell e o Compose não define nada, sem apresentar qualquer aviso. É importante conhecer as falhas silenciosas de transferência, porque um serviço que arranca com uma variável de palavra-passe vazia geralmente inicia sem problemas e fica simplesmente totalmente exposto.
Qual valor prevalece
O Docker documenta a ordem de precedência, da mais alta para a mais baixa: docker compose run -e na linha de comandos, depois environment ou env_file, cujo valor é interpolado a partir da shell ou de um ficheiro de ambiente, depois environment simples no ficheiro Compose, depois env_file e, por fim, a diretiva ENV incorporada na imagem.
A versão curta para o trabalho diário: environment: prevalece sobre env_file:, e -e na linha de comandos prevalece sobre ambos. Comprove isso num único ficheiro.
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGO primeiro comando imprime from_environment, porque environment: substituiu o valor em app.env. O segundo imprime from_cli. Nada no ficheiro Compose substitui a linha de comandos.
Quando um contentor se comporta como se a sua configuração nunca tivesse sido aplicada, não tente adivinhar. docker compose config imprime o ficheiro totalmente resolvido, e docker compose config --environment imprime as variáveis de interpolação que o Compose está a utilizar. A maioria dos relatos de que “o meu ficheiro de ambiente é ignorado” resulta de um valor definido duas vezes, em níveis diferentes.
Por que as variáveis de ambiente vazam
Defina uma palavra-passe em environment: e ela será armazenada na configuração do contentor em disco, ficando visível para qualquer utilizador do grupo docker.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'A saída contém "DB_PASSWORD=hunter2" em texto simples. Mais três caminhos expõem o mesmo valor. docker compose config imprime-o no terminal, que é como acaba colado num fórum de suporte. Os processos filhos herdam a variável por predefinição, a menos que o processo-pai os inicie deliberadamente com um ambiente de substituição. Um processo executado como o mesmo utilizador que o PID 1, ou que tenha CAP_SYS_PTRACE, pode lê-la a partir de /proc/1/environ. Num contentor onde tudo é executado como root, isso aplica-se a todos os processos. Uma aplicação executada como utilizador sem privilégios enquanto o PID 1 é executado como root recebe um erro de permissões nesse ficheiro, mas continua a manter a variável no seu próprio ambiente. Além disso, os gestores de falhas das aplicações despejam habitualmente todo o ambiente num log ou num relatório de erro.
A pertença ao grupo docker equivale efetivamente a root no host. Portanto, não pode tratar isto como um limite de privilégios. O guia sobre contas de utilizador com privilégios mínimos numa VPS explica por que motivo vale a pena restringir esse grupo em qualquer servidor partilhado.
Os secrets do Compose mantêm o valor num ficheiro
O Compose suporta secrets baseados em ficheiros. O valor é montado no contentor como um ficheiro, em vez de ser injetado no ambiente.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtO secret é montado em /run/secrets/db_password dentro do contentor. O nome depois da barra é o nome do secret definido no bloco de nível superior secrets:.
O sufixo _FILE é uma convenção usada pelas Docker Official Images, incluindo postgres, mysql e mariadb. Esses scripts de entrypoint verificam a existência de VARNAME_FILE, leem o ficheiro e usam o seu conteúdo. Isto não é uma funcionalidade do Docker. Só funciona quando a imagem a implementa. Consulte a documentação da imagem antes de assumir que SOMETHING_FILE será respeitado. As aplicações que não suportam esta convenção podem muitas vezes ler o ficheiro durante o arranque. Em alternativa, pode passar o caminho e deixar que o seu próprio entrypoint faça essa leitura.
Verifique a partir do contentor em execução:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDO primeiro comando imprime a palavra-passe. O segundo não imprime nada, porque o valor nunca entrou no ambiente. Esse é o objetivo: docker inspect neste contentor mostra apenas o caminho inofensivo.
Proteja o ficheiro de origem no host, porque o secret só é privado enquanto o ficheiro que o contém também o for:
chmod 600 db_password.txtO meio-termo pragmático numa VPS
Muitas imagens self-hosted não suportam variáveis _FILE, por isso as variáveis de ambiente são a única forma de fornecer estes valores. Numa VPS administrada por uma única pessoa, o objetivo realista é impedir que os valores fiquem num ficheiro legível por todos no diretório do projeto e mantê-los fora do git.
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 cria o ficheiro com as permissões já definidas, por isso não existe um intervalo em que fique legível por todos. O root é o proprietário, portanto um utilizador que não seja root no servidor não o consegue ler, embora qualquer pessoa que consiga executar docker ainda possa obter o valor a partir do contentor. Adicione *.env e .env a .gitignore e faça commit de um app.env.example que contenha os nomes das chaves com valores vazios. Uma palavra-passe incluída num commit é uma palavra-passe que deve ser substituída.
A rotação de um valor implica reiniciar o serviço. As variáveis de ambiente são lidas uma vez, quando o processo do contentor arranca, por isso editar o ficheiro não altera nada até executar docker compose up -d --force-recreate db. Este é o mesmo padrão utilizado no guia n8n atrás de HTTPS numa VPS, em que a chave de encriptação fica fora do ficheiro compose.
Dividir a configuração por ambiente
O Compose lê .env do diretório do projeto por padrão. Aponte-o para outro local com --env-file.
docker compose --env-file .env.staging configVários ficheiros são lidos pela ordem indicada, e os ficheiros posteriores substituem os anteriores. Mantenha os valores predefinidos não secretos num ficheiro versionado e os segredos num ficheiro que nunca saia do servidor. O mesmo se aplica a env_file:: para uma chave duplicada, prevalece o último ficheiro listado.
FAQ
Por que o meu ficheiro .env é ignorado dentro do contentor?
Ele não é ignorado. O ficheiro .env substitui os marcadores ${NAME} no ficheiro Compose e também pode definir variáveis do próprio Compose, como COMPOSE_PROJECT_NAME ou COMPOSE_FILE. Ele nunca define variáveis dentro de um contentor. Para passar o valor para o contentor, faça referência a ele: environment: { KEY: "${NAME}" }, ou use env_file: ./that-file.env.
environment substitui env_file ou acontece o contrário?
environment: tem precedência. A ordem documentada pelo Docker coloca o atributo environment acima do atributo env_file, e ambos ficam abaixo de docker compose run -e na linha de comandos. Se uma chave estiver definida nos dois locais, o valor em env_file não é utilizado e não é apresentado nenhum aviso.
Como vejo o valor final que o Compose vai utilizar?
Execute docker compose config para imprimir o ficheiro Compose totalmente resolvido, com toda a interpolação aplicada. Para um contentor que já está em execução, docker inspect <container> --format '{{json .Config.Env}}' mostra exatamente o que o processo recebeu.
Os secrets do Compose são encriptados?
Não. Um secret baseado num ficheiro é montado no contentor como um ficheiro simples em /run/secrets/<name>, e o ficheiro de origem fica no disco do host sem encriptação. A vantagem é o âmbito, não a encriptação: o valor não aparece no ambiente do contentor, no resultado de docker inspect nem em dumps de falhas que imprimam o ambiente.
Posso usar aspas e espaços num ficheiro env?
Sim. O Compose remove as aspas exteriores, por isso KEY="value with spaces" e KEY=value with spaces produzem ambos value with spaces. Use aspas simples quando o valor contiver um $ que não deve ser interpolado e coloque entre aspas qualquer valor que contenha #, porque, sem aspas, isso inicia um comentário. Os espaços à volta de = são removidos, por isso KEY = value ainda define KEY.