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.
As três coisas que as pessoas chamam de ficheiro env
O Docker Compose tem três mecanismos distintos com nomes muito semelhantes, o que pode causar confusão. O ficheiro .env preenche os marcadores ${VARIABLE} no próprio compose.yaml, antes de o Compose analisar o ficheiro. O atributo env_file: carrega um ficheiro com pares chave/valor para o ambiente do contentor. O atributo environment: define diretamente variáveis no contentor, escritas 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. Em seguida, aborda o ponto mais importante: qualquer pessoa que possa executar docker inspect consegue ler as variáveis de ambiente. 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 numa VPS e volte aqui para tratar da configuraçã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 efetivamente analisou.
docker compose configO resultado mostra image: alpine:3.20. O marcador de posição desapareceu porque a interpolação ocorreu durante a análise. O Compose procura .env no diretório do projeto, que é o diretório que contém o ficheiro Compose, e substitui todos os ${NAME} que encontrar.
Em seguida, 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 com POSTGRES_PASSWORD=hunter2 não produz qualquer efeito na base de dados, a menos que alguma parte do ficheiro Compose faça referência a ele.
${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 opção correta para um valor que não tem um valor predefinido seguro.
env_file carrega variáveis para o container
O atributo env_file: especifica um ou mais ficheiros cujo conteúdo se torna variáveis de ambiente do container.
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 é shell. Na maioria dos casos, as aspas permanecem como parte do valor, e não é necessário usar prefixos export. Não coloque espaços em redor do sinal =, porque KEY = value cria uma variável literalmente chamada KEY , com um espaço inicial no valor.
Um caminho env_file em falta é um erro e o Compose é interrompido. Marque-o como opcional se o ficheiro puder legitimamente não existir:
env_file:
- path: ./app.env
required: falsedefine variáveis no ambiente na mesma linha
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. O formato de lista tem um recurso adicional: uma chave isolada, sem valor, transmite a variável a partir da shell onde executou docker compose.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoIsto imprime from_my_shell. Se o executar sem definir GREETING na shell, o Compose não define nada e não apresenta qualquer aviso. É importante conhecer estas falhas silenciosas de transmissão, porque um serviço que arranca com uma variável de palavra-passe vazia normalmente inicia sem problemas e fica simplesmente desprotegido.
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 no ficheiro de configuração do contentor, 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, fazendo com que acabe colado num fórum de suporte. Qualquer processo dentro do contentor pode ler /proc/1/environ, e todos os processos filho herdam a variável. Além disso, os gestores de falhas das aplicações costumam despejar todo o ambiente num log ou num relatório de erro.
A pertença ao grupo docker equivale efetivamente a root no host. Por isso, não deve ser considerada uma fronteira de privilégios. O guia sobre contas de utilizador com privilégios mínimos num VPS explica por que razão deve 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 arquivo .env é ignorado dentro do container?
Ele não é ignorado. O arquivo .env apenas substitui os marcadores ${NAME} no arquivo Compose. Ele nunca define variáveis dentro de um container. Para passar o valor para o container, faça referência a ele: environment: { KEY: "${NAME}" }, ou use env_file: ./that-file.env.
environment substitui env_file ou 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 comando. Se uma chave for definida nos dois locais, o valor em env_file será silenciosamente ignorado.
Como vejo o valor final que o Compose usará?
Execute docker compose config para exibir o arquivo Compose totalmente resolvido, com toda a interpolação aplicada. Para um container 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 criptografados?
Não. Um secret baseado em arquivo é montado no container como um arquivo sem criptografia em /run/secrets/<name>, e o arquivo de origem fica armazenado no disco do host sem criptografia. O benefício é o escopo, não a criptografia: o valor não aparece no ambiente do container, na saída de docker inspect nem em dumps de falha que imprimem o ambiente.
Posso usar aspas e espaços em um arquivo de ambiente?
Use KEY=value with spaces e não coloque as aspas. O Compose trata todo o restante da linha como o valor, portanto as aspas normalmente acabam como caracteres literais no valor. Nunca coloque espaços ao redor de =, pois a chave passa a conter um espaço no final e nada corresponde a ela.