Docker Compose: differenze tra .env, env_file e secrets
Scopri come .env, env_file e environment gestiscono le variabili in Docker Compose, quale valore prevale e perché le password devono stare nei secrets.
I tre elementi che vengono chiamati file env
Docker Compose dispone di tre meccanismi distinti, con nomi molto simili e quindi facilmente confondibili. Il file .env sostituisce i segnaposto ${VARIABLE} direttamente nel file compose.yaml, prima ancora che Compose lo analizzi. L'attributo env_file: carica nel container un file contenente coppie chiave/valore per l'ambiente. L'attributo environment: imposta direttamente le variabili del container, scrivendole nel file Compose. I tre meccanismi non sono intercambiabili. Quando due di essi impostano la stessa chiave, il risultato è determinato da un ordine di precedenza documentato.
Questa guida mostra ogni meccanismo in funzione e dimostra la precedenza con un comando eseguibile. Illustra poi l'aspetto più importante: le variabili d'ambiente sono leggibili da chiunque possa eseguire docker inspect, quindi le password non devono essere inserite al loro interno. Se non hai ancora familiarità con i file Compose, inizia da Nozioni di base su Docker Compose su un VPS e torna qui per la configurazione.
Il file .env è destinato al file Compose, non al container
Create una directory e inserisci al suo interno due file.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGOra chiedi a Compose che cosa ha effettivamente analizzato.
docker compose configL'output mostra image: alpine:3.20. Il segnaposto è scomparso perché l'interpolazione è stata eseguita al momento dell'analisi. Compose cerca .env nella directory del progetto, cioè la directory che contiene il file Compose, e sostituisce ogni occorrenza di ${NAME}.
Ora avvia il servizio.
docker compose run --rm demoprintenv ALPINE_TAG termina con lo stato 1 e non stampa nulla. La variabile non esiste all'interno del container. Questo è il malinteso più comune: .env ha configurato il file Compose, non il processo. Un file .env che contiene POSTGRES_PASSWORD=hunter2 non produce alcun effetto sul database, a meno che una parte del file Compose non vi faccia riferimento.
${NAME:-default} fornisce un valore predefinito quando la variabile non è impostata o è vuota. ${NAME:?message} fa sì che Compose rifiuti di avviarsi e stampi il messaggio specificato. È la scelta corretta per un valore che non dispone di un valore predefinito sicuro.
env_file carica le variabili nel container
L'attributo env_file: indica uno o più file il cui contenuto viene utilizzato come variabili d'ambiente del 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 demoQuesto stampa from_env_file. Il formato del file consiste in semplici righe KEY=value, una per riga, con # all'inizio per indicare un commento. Non è una shell. Nella maggior parte dei casi, le virgolette restano parte del valore e i prefissi export non sono necessari. Non inserire spazi intorno al simbolo =, perché KEY = value crea una variabile chiamata letteralmente KEY , con uno spazio iniziale nel valore.
Un percorso env_file mancante genera un errore e Compose si arresta. Contrassegnalo come facoltativo se il file può legittimamente non essere presente:
env_file:
- path: ./app.env
required: falseenvironment imposta le variabili inline
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentSono accettate due sintassi: la forma a mappa riportata sopra e una forma a elenco che usa - GREETING=from_environment. Il comportamento è identico. La forma a elenco offre un'opzione aggiuntiva: una chiave senza valore trasferisce la variabile dalla shell in cui è stato eseguito docker compose.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoQuesto stampa from_my_shell. Se lo si esegue senza impostare GREETING nella shell, Compose non imposta nulla e non mostra alcun avviso. È importante conoscere questi errori di trasferimento silenziosi: un servizio avviato con una variabile della password vuota spesso parte correttamente, ma rimane completamente esposto.
Quale valore prevale
Docker documenta l’ordine di precedenza, dal più alto al più basso: docker compose run -e nella riga di comando, quindi environment o env_file, il cui valore viene interpolato dalla shell o da un file env, quindi il semplice environment nel file Compose, poi env_file, infine la direttiva ENV incorporata nell’immagine.
In breve, per l’uso quotidiano: environment: prevale su env_file:, mentre -e nella riga di comando prevale su entrambi. Verificalo in un unico file.
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 GREETINGIl primo comando stampa from_environment, quindi environment: ha sostituito il valore in app.env. Il secondo stampa from_cli. Nulla nel file Compose può sostituire il valore specificato nella riga di comando.
Quando un container si comporta come se la configurazione non fosse stata applicata, non procedere per tentativi. docker compose config stampa il file completamente risolto, mentre docker compose config --environment stampa le variabili usate da Compose per l’interpolazione. Nella maggior parte dei casi, i problemi segnalati come “il mio file env viene ignorato” dipendono da un valore impostato due volte a livelli diversi.
Perché le variabili d'ambiente possono fuoriuscire
Imposta una password in environment: e questa viene memorizzata su disco nella configurazione del container, dove è visibile a qualsiasi utente appartenente al gruppo docker.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'L'output contiene "DB_PASSWORD=hunter2" in testo non cifrato. Altri tre percorsi espongono lo stesso valore. docker compose config lo stampa nel terminale, ed è così che può finire incollato in un forum di supporto. Qualsiasi processo all'interno del container può leggere /proc/1/environ e ogni processo figlio eredita la variabile. Inoltre, i gestori degli arresti anomali delle applicazioni riversano regolarmente l'intero ambiente in un log o in un report di errore.
L'appartenenza al gruppo docker equivale di fatto ad avere privilegi root sull'host. Non è quindi un confine di privilegio su cui fare affidamento. La guida agli account utente con privilegi minimi su un VPS spiega perché è opportuno limitare l'accesso a questo gruppo su qualsiasi server condiviso.
I secret di Compose mantengono il valore in un file
Compose supporta i secret basati su file. Il valore viene montato nel container come file invece di essere inserito nell'ambiente.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtIl secret viene montato in /run/secrets/db_password all'interno del container. Il nome dopo la barra è il nome del secret definito nel blocco secrets: di primo livello.
Il suffisso _FILE è una convenzione usata dalle Docker Official Images, incluse postgres, mysql e mariadb. I relativi script entrypoint verificano la presenza di VARNAME_FILE, leggono il file e ne usano il contenuto. Non è una funzionalità di Docker, quindi funziona solo quando l'immagine la implementa. Consultare la documentazione dell'immagine prima di presumere che SOMETHING_FILE venga rispettato. Le applicazioni che non lo supportano spesso possono leggere direttamente il file all'avvio. In alternativa, è possibile passare il percorso e lasciare che sia il proprio entrypoint a gestirlo.
Verificare dall'interno del container in esecuzione:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDIl primo comando stampa la password. Il secondo non stampa nulla, perché il valore non è mai entrato nell'ambiente. Questo è lo scopo: docker inspect su questo container mostra solo il percorso, che non contiene informazioni sensibili.
Proteggere il file sorgente sull'host, perché il secret è riservato solo quanto il file che lo contiene:
chmod 600 db_password.txtIl compromesso pratico su un VPS
Molte immagini self-hosted non supportano le variabili _FILE, quindi le variabili d’ambiente sono l’unico modo per fornire questi valori. Su un VPS gestito da un solo amministratore, l’obiettivo realistico è impedire che i valori restino in un file leggibile da tutti nella directory del progetto e tenerli fuori da 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 crea il file con i permessi già impostati, quindi non esiste un intervallo in cui il file sia leggibile da chiunque. Il file appartiene a root, quindi un utente non root del server non può leggerlo. Tuttavia, chiunque possa eseguire docker può comunque leggere il valore dal container. Aggiungi *.env e .env a .gitignore e sottoponi al commit un file app.env.example che contenga invece i nomi delle chiavi con valori vuoti. Una password sottoposta al commit è una password da ruotare.
Ruotare un valore significa riavviare il servizio. Le variabili d’ambiente vengono lette una sola volta, quando viene avviato il processo del container. Modificare il file non produce quindi alcun effetto finché non esegui docker compose up -d --force-recreate db. È lo stesso schema usato nella guida n8n dietro HTTPS su un VPS, in cui la chiave di crittografia si trova all’esterno del file compose.
Suddividere la configurazione per ambiente
Compose legge .env dalla directory del progetto per impostazione predefinita. Per indicare un percorso diverso, usare --env-file.
docker compose --env-file .env.staging configI file multipli vengono letti nell'ordine indicato e quelli successivi sovrascrivono quelli precedenti. Mantenere i valori predefiniti non riservati in un file sottoposto a commit e i secret in un file che non deve mai lasciare il server. Lo stesso vale per env_file:: in caso di chiave duplicata, prevale quella dell'ultimo file elencato.
FAQ
Perché il file .env viene ignorato all'interno del container?
Non viene ignorato. Il file .env sostituisce soltanto i segnaposto ${NAME} nel file Compose. Non imposta mai le variabili all'interno di un container. Per trasferire il valore nel container, fai riferimento a environment: { KEY: "${NAME}" } oppure usa env_file: ./that-file.env.
Ha la precedenza environment su env_file o il contrario?
Prevale environment:. L'ordine documentato da Docker colloca l'attributo environment sopra l'attributo env_file; entrambi sono subordinati a docker compose run -e nella riga di comando. Se una chiave è impostata in entrambi i punti, il valore in env_file non viene utilizzato e non viene generato alcun avviso.
Come posso vedere il valore finale che Compose utilizzerà?
Esegui docker compose config per stampare il file Compose completamente risolto, con tutte le sostituzioni applicate. Per un container già in esecuzione, docker inspect <container> --format '{{json .Config.Env}}' mostra esattamente ciò che il processo ha ricevuto.
I secret di Compose sono crittografati?
No. Un secret basato su file viene montato nel container come file non crittografato in /run/secrets/<name>, mentre il file sorgente rimane non crittografato sul disco dell'host. Il vantaggio riguarda l'ambito di esposizione, non la crittografia: il valore resta fuori dall'ambiente del container, dall'output di docker inspect e dai crash dump che stampano l'ambiente.
Posso usare virgolette e spazi in un file env?
Usa KEY=value with spaces e non inserire le virgolette. Compose considera l'intera parte restante della riga come valore; di conseguenza, le virgolette finiscono generalmente per diventare caratteri letterali del valore. Non inserire mai spazi attorno a =, perché la chiave conterrebbe uno spazio finale e nessuna corrispondenza verrebbe trovata.