SSD Nodes Learn 8GB di RAM — $66/anno
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-02

Docker Compose: differenze tra .env, env_file e secrets

Scopri la differenza tra .env, env_file ed environment in Docker Compose, l’ordine di precedenza e perché le password vanno gestite con secrets.

Le tre cose che vengono chiamate file env

Docker Compose dispone di tre meccanismi distinti con nomi molto simili, che possono creare confusione. Il file .env sostituisce i segnaposto ${VARIABLE} direttamente in compose.yaml, prima ancora che Compose analizzi il file. L'attributo env_file: carica nel container un file di coppie chiave/valore per l'ambiente. L'attributo environment: imposta direttamente le variabili nel container, specificandole nel file Compose. Non sono intercambiabili. Quando due di questi meccanismi impostano la stessa chiave, il valore finale dipende da un ordine di precedenza documentato.

Questa guida mostra il funzionamento di ciascun meccanismo e dimostra la precedenza con un comando eseguibile. In seguito tratta 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 familiarità con i file Compose, inizia da Nozioni di base su Docker Compose su un VPS e poi torna qui per la configurazione.

Il file .env serve al file Compose, non al container

Create una directory e inseritevi due file.

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

Ora chiedete a Compose che cosa ha effettivamente analizzato.

docker compose config

L'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 ${NAME} che trova.

Poi avviate il servizio.

docker compose run --rm demo

printenv ALPINE_TAG termina con lo stato 1 e non stampa nulla. La variabile non esiste all'interno del container. Questo è l'equivoco 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 alternativo 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 ha un valore predefinito sicuro.

env_file carica le variabili nel container

L'attributo env_file: specifica uno o più file il cui contenuto diventa costituito da variabili d'ambiente del container.

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

Questo stampa from_env_file. Il formato del file è costituito da righe KEY=value semplici, una per riga, con # all'inizio per indicare un commento. Non è sintassi della shell. Nella maggior parte dei casi, le virgolette rimangono parte del valore e i prefissi export non sono necessari. Non inserire spazi intorno al simbolo =, perché KEY = value crea una variabile denominata letteralmente KEY , con uno spazio iniziale nel valore.

Un percorso env_file mancante è un errore e Compose si arresta. Contrassegnalo come facoltativo se il file può legittimamente non essere presente:

    env_file:
      - path: ./app.env
        required: false

environment imposta le variabili inline

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

Sono accettate due sintassi: la forma a mappatura riportata sopra e una forma a elenco che usa - GREETING=from_environment. Il loro comportamento è identico. La forma a elenco offre un'opzione aggiuntiva: una chiave senza valore trasferisce la variabile dall'ambiente della shell in cui è stato eseguito docker compose.

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

Stampa from_my_shell. Se lo si esegue senza impostare GREETING nella shell, Compose non imposta alcun valore e non mostra avvisi. È importante conoscere gli errori silenziosi nel trasferimento delle variabili: un servizio che si avvia con una variabile della password vuota spesso si avvia correttamente, ma rimane completamente esposto.

Quale prevale

Docker documenta l'ordine di precedenza, dal più alto al più basso: docker compose run -e nella riga di comando, poi environment o env_file il cui valore viene interpolato dalla shell o da un file env, poi environment semplice nel file Compose, poi env_file, infine la direttiva ENV incorporata nell'immagine.

La versione breve per il lavoro quotidiano: environment: prevale su env_file: e -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_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

Il primo comando stampa from_environment, quindi environment: ha sostituito il valore in app.env. Il secondo stampa from_cli. Nulla nel file Compose sostituisce il valore della riga di comando.

Quando un container si comporta come se la configurazione non fosse stata applicata, non fare supposizioni. docker compose config stampa il file completamente risolto e docker compose config --environment stampa le variabili di interpolazione utilizzate da Compose. La maggior parte delle segnalazioni secondo cui "il mio file env viene ignorato" è dovuta a un valore impostato due volte, a due livelli diversi.

Perché le variabili d'ambiente vengono esposte

Impostare una password in environment: la memorizza nella configurazione del container su disco, dove è visibile a qualsiasi utente del 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, che è il motivo per cui finisce per essere 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 scaricano spesso l'intero ambiente in un log o in un rapporto di errore.

L'appartenenza al gruppo docker equivale di fatto all'accesso root sull'host. Non è quindi un confine di privilegi su cui fare affidamento. La guida sugli account utente con privilegi minimi su un VPS spiega perché è opportuno limitare questo gruppo su qualsiasi sistema condiviso.

I secret di Compose conservano 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.txt

Il secret viene montato in /run/secrets/db_password all'interno del container. Il nome dopo la barra è il nome del secret del blocco secrets: di primo livello.

Il suffisso _FILE è una convenzione usata dalle Docker Official Images, tra cui 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_PASSWORD

Il primo comando stampa la password. Il secondo non stampa nulla, perché il valore non è mai entrato nell'ambiente. Questo è lo scopo: docker inspect in questo container mostra solo il percorso, che non contiene informazioni sensibili.

Proteggere il file sorgente sull'host, perché il secret è privato solo quanto il file che lo contiene:

chmod 600 db_password.txt

Il compromesso pragmatico su un VPS

Molte immagini self-hosted non supportano le variabili _FILE, quindi le variabili d'ambiente sono l'unico modo per passare i valori. Su un singolo VPS amministrato da una sola persona, l'obiettivo realistico è impedire che i valori restino in un file leggibile da tutti nella directory del progetto e mantenerli 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.env

install -m 600 crea il file con i permessi già impostati, quindi non esiste un intervallo durante il quale sia leggibile da tutti. Il proprietario è root, quindi un utente non root del sistema non può leggerlo, anche se chiunque possa eseguire docker può comunque leggere il valore dal container. Aggiungi *.env e .env a .gitignore e fai il commit di un file app.env.example che contenga invece i nomi delle chiavi con valori vuoti. Una password sottoposta a 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, quindi la modifica del file non produce 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 fuori dal file compose.

Suddivisione della configurazione per ambiente

Per impostazione predefinita, Compose legge .env dalla directory del progetto. Per indicare un percorso diverso, usare --env-file.

docker compose --env-file .env.staging config

I file vengono letti in ordine e quelli successivi sovrascrivono quelli precedenti. Mantenere i valori predefiniti non segreti in un file sottoposto a commit e i segreti in un file che non lasci mai il server. Lo stesso vale per env_file:: in caso di chiavi duplicate, prevale l'ultimo file elencato.

FAQ

Perché il file .env viene ignorato all'interno del container?

Non viene ignorato. Il file .env sostituisce solo i segnaposto ${NAME} nel file Compose. Non imposta mai 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 viceversa?

Ha la precedenza environment:. L'ordine documentato da Docker colloca l'attributo environment al di sopra dell'attributo env_file; entrambi sono inferiori a docker compose run -e nella riga di comando. Se una chiave è impostata in entrambi i punti, il valore in env_file viene ignorato senza messaggi.

Come posso vedere il valore finale che verrà usato da Compose?

Esegui docker compose config per stampare il file Compose completamente risolto, con tutta l'interpolazione applicata. Per un container già in esecuzione, docker inspect <container> --format '{{json .Config.Env}}' mostra esattamente ciò che il relativo processo ha ricevuto.

I secrets di Compose sono crittografati?

No. Un secret basato su file viene montato nel container come file di testo in /run/secrets/<name>, mentre il file sorgente rimane non crittografato sul disco dell'host. Il vantaggio riguarda l'ambito, 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 tutto il resto della riga come valore, quindi le virgolette finiscono generalmente come caratteri letterali nel valore. Non inserire mai spazi attorno a =, perché la chiave conterrà uno spazio finale e nessuna corrispondenza verrà trovata.