Padroneggiare le Variabili d'Ambiente in Docker: Configurazione vs. Segreti
Sblocca distribuzioni Docker sicure e flessibili padroneggiando le variabili d'ambiente. Questa guida completa chiarisce la distinzione critica tra l'uso delle variabili d'ambiente per la configurazione generale dell'applicazione e la gestione sicura dei dati sensibili come chiavi API e password. Impara metodi pratici per passare impostazioni non sensibili, comprendi i gravi rischi di esporre segreti tramite variabili d'ambiente e scopri come sfruttare Docker Secrets e Compose per una gestione robusta e crittografata dei segreti. Eleva la tua conoscenza di Docker e proteggi le tue applicazioni.
Padroneggiare le Variabili d'Ambiente in Docker: Configurazione vs. Segreti
Le variabili d'ambiente sono comode in Docker perché permettono alla stessa immagine di funzionare in sviluppo, staging e produzione con impostazioni diverse. Questa comodità diventa rischiosa quando i team inseriscono password, chiavi di firma e token API nello stesso contenitore dei livelli di log e dei numeri di porta.
Il modello mentale pulito è semplice: le variabili d'ambiente vanno bene per la configurazione runtime non sensibile. I segreti dovrebbero provenire da un archivio di segreti o da un file di segreti montato, con accesso limitato e un piano di rotazione.
Comprendere le Variabili d'Ambiente per la Configurazione
Le variabili d'ambiente sono un metodo diretto e ampiamente adottato per passare la configurazione runtime alle applicazioni, incluse quelle in esecuzione in container Docker. Permettono di modificare il comportamento di un'applicazione senza ricostruire l'immagine Docker, rendendo i tuoi container più flessibili e portatili. Questo è ideale per impostazioni dinamiche non sensibili come numeri di porta dell'applicazione, flag di debug o URL di servizi di terze parti.
Metodi per Passare le Variabili di Configurazione
Docker fornisce diversi modi per definire e iniettare variabili d'ambiente nei tuoi container:
1. Istruzione ENV nel Dockerfile
L'istruzione ENV imposta una variabile d'ambiente predefinita che sarà disponibile all'interno del container quando viene eseguito. Questo è adatto per variabili che è improbabile che cambino o che forniscono valori predefiniti sensati per la tua applicazione.
FROM alpine:latest
ENV APP_PORT=8080
ENV DEBUG_MODE=false
COPY ./app /app
WORKDIR /app
CMD ["/app/start.sh"]
Suggerimento: Mentre ENV imposta i valori predefiniti, questi possono essere sovrascritti in fase di esecuzione.
2. Flag -e o --env con docker run
Quando si avvia un singolo container, puoi usare il flag -e o --env per passare le variabili d'ambiente direttamente. Questo è comune per test ad hoc o per fornire impostazioni specifiche che differiscono dai valori predefiniti del 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 in Docker Compose
Per gestire più variabili d'ambiente, specialmente su più servizi definiti in un file docker-compose.yml, l'opzione env_file è molto comoda. Permette di caricare variabili da uno o più file .env, mantenendo il tuo docker-compose.yml più pulito.
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. Chiave environment in Docker Compose
In alternativa, puoi definire le variabili d'ambiente direttamente all'interno della sezione environment di un servizio in docker-compose.yml. Questo è spesso preferito per un piccolo numero di variabili o per variabili specifiche di un singolo servizio.
version: '3.8'
services:
webapp:
image: my_app_image:latest
ports:
- "80:8080"
environment:
APP_PORT: 8080
DEBUG_MODE: false
Le Insidie dell'Uso delle Variabili d'Ambiente per i Segreti
Mentre le variabili d'ambiente sono eccellenti per la configurazione, fondamentalmente non sono sicure per la gestione di dati sensibili (segreti) come password di database, chiavi API o chiavi SSH private. Questa è una vulnerabilità di sicurezza critica che spesso viene trascurata, specialmente negli ambienti di sviluppo.
Perché le Variabili d'Ambiente Non Sono Sicure per i Segreti:
Visibilità tramite
docker inspect: Chiunque abbia accesso all'host Docker può facilmente visualizzare le variabili d'ambiente di un container in esecuzione usandodocker inspect <container_id>. Questo significa che i tuoi segreti sono chiaramente visibili in testo semplice.# Esempio di esposizione di un segreto (NON FARLO IN PRODUZIONE) docker run -d -e DB_PASSWORD=mysecretpassword --name insecure_app nginx:latest # Chiunque può vedere la password docker inspect insecure_app | grep DB_PASSWORDSnooping dei Processi: All'interno del container, altri processi o utenti (se esistono più utenti) potrebbero essere in grado di leggere le variabili d'ambiente, specialmente se l'applicazione viene eseguita come root o ha privilegi elevati.
Registrazione e Cronologia: Le variabili d'ambiente possono finire inavvertitamente nei log, nella cronologia della pipeline CI/CD o nella cronologia della shell, portando a un'esposizione accidentale.
Layer dell'Immagine: Se usi
ENVin un Dockerfile con un segreto, quel segreto viene incorporato in un layer dell'immagine e rimane lì, anche se provi aunsetin un layer successivo. Questo rende il segreto recuperabile dall'immagine stessa.Condivisione Accidentale: I file
.envodocker-compose.ymlcontenenti segreti vengono spesso committati nei sistemi di controllo versione o condivisi in modo inappropriato, portando a un'esposizione diffusa.
Avvertenza: Trattare le informazioni sensibili come normali variabili d'ambiente è un errore di sicurezza comune. Presumi sempre che le variabili d'ambiente siano pubblicamente visibili sull'host e all'interno del container.
Gestire in Modo Sicuro i Segreti in Docker
Per affrontare le carenze di sicurezza delle variabili d'ambiente per i dati sensibili, Docker fornisce capacità dedicate di gestione dei segreti, principalmente attraverso Docker Secrets (per Docker Swarm) e strumenti esterni come Docker Compose con funzionalità secrets (che può sfruttare i segreti di Docker Swarm o semplicemente montare file).
Docker Secrets (Modalità Docker Swarm)
Docker Secrets è una funzionalità integrata con la modalità Docker Swarm che fornisce un modo sicuro per trasmettere e archiviare dati sensibili per i servizi. I segreti sono:
- Crittografati a riposo nei log Raft del manager Swarm.
- Trasmessi in modo sicuro alle attività di servizio autorizzate.
- Montati come file in memoria all'interno del filesystem del container, tipicamente in
/run/secrets/<nome_segreto>, piuttosto che esposti come variabili d'ambiente. - Accessibili solo dai servizi a cui è stato esplicitamente concesso l'accesso.
Come Usare Docker Secrets (Modalità Swarm)
- Inizializza Swarm (se non già fatto) :
docker swarm init ```
- Crea un Segreto: I segreti vengono creati da un file o dall'input standard.
echo "my_secure_db_password" | docker secret create db_password_secret - echo "SG.your_api_key_here" | docker secret create sendgrid_api_key - ```
- Distribuisci un Servizio con il Segreto: I servizi fanno riferimento ai segreti per nome. Docker monta il segreto nel container.
docker service create --name my-webapp
--secret db_password_secret
--secret sendgrid_api_key
my_app_image:latest
```
- Accesso ai Segreti nel Container: Le applicazioni leggono il segreto dal percorso del file montato.
Nel codice della tua applicazione Python (o simile per altri linguaggi)
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 Segreti (per host singolo o Swarm)
Docker Compose versione 3.1+ ha introdotto una sezione secrets, che ti permette di definire e fare riferimento ai segreti all'interno del tuo docker-compose.yml. Quando viene eseguito in modalità Swarm, Compose sfrutta i segreti nativi di Docker Swarm. Quando viene eseguito su un singolo host senza modalità Swarm, Compose supporta ancora i segreti montando i file dall'host nel container in modo sicuro, anche se senza la crittografia a riposo fornita da Swarm.
Usare secrets in docker-compose.yml
Definisci i Segreti: Puoi definire i segreti facendo riferimento a un file esterno o rendendolo un segreto esterno (segreto Swarm pre-creato).
# 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 # Percorso di un file sull'host contenente la password sendgrid_api_key: external: true # Si riferisce a un segreto Docker Swarm preesistente chiamato 'sendgrid_api_key'Crea File di Segreti Locali (se viene usato
file) :
mkdir secrets echo "my_local_db_password" > ./secrets/db_password.txt ```
Distribuisci con Compose:
docker compose up -ddistribuirà i tuoi servizi, rendendo i segreti disponibili in/run/secrets/<nome_segreto>all'interno dei container.# All'interno del container, il contenuto di ./secrets/db_password.txt sarà in: # /run/secrets/db_password
Scegliere lo Strumento Giusto: Configurazione vs. Segreti
La decisione se usare una variabile d'ambiente per la configurazione o una soluzione di gestione dei segreti dedicata si riduce a una domanda principale:
Il dato è sensibile?
- Se Sì (dato sensibile): Usa Docker Secrets (con Swarm) o un sistema di gestione dei segreti simile (es. Kubernetes Secrets, HashiCorp Vault). Per configurazioni Compose su host singolo, usa la sezione
secretsper montare i file in modo sicuro. - Se No (configurazione non sensibile): Usa variabili d'ambiente (tramite
ENVnel Dockerfile, flag-e,env_fileoenvironmentin Compose).
| Caratteristica | Variabili d'Ambiente (per configurazione) | Docker Secrets (per dati sensibili) |
|---|---|---|
| Scopo | Configurazione applicativa non sensibile | Dati sensibili come password e chiavi API |
| Visibilità | Visibile tramite docker inspect e spesso ispezione dei processi |
Montato come file; non mostrato come valori di ambiente normali |
| Sicurezza | Non appropriato per dati sensibili | Gestione più forte; i segreti Swarm sono crittografati a riposo, mentre i segreti dei file Compose locali dipendono dalla protezione del file host |
| Accesso nell'App | Letto da os.environ o simile |
Letto dal file /run/secrets/<nome_segreto> |
| Gestito da | Runtime Docker, Docker Compose | Docker Swarm, Docker Compose o un gestore di segreti esterno |
| Casi d'Uso | Numeri di porta, flag di debug, URL non sensibili | Password di database, token API, chiavi private |
Migliori Pratiche per Entrambi
Per la Configurazione (Variabili d'Ambiente):
- Fornisci valori predefiniti sensati nel tuo Dockerfile usando
ENV. Questo rende le tue immagini eseguibili immediatamente e documenta chiaramente le variabili previste. - Esternalizza la configurazione dove possibile. Usa file
.envcondocker composeo servizi di configurazione esterni per distribuzioni più grandi. - Documenta tutte le opzioni di configurazione e i loro valori previsti, magari in un
README.mdo nella documentazione dell'applicazione. - Evita di hardcodare valori che potrebbero cambiare tra ambienti (sviluppo, staging, produzione).
Per i Segreti (Docker Secrets e oltre):
- Non committare mai i segreti (es. file
.envcontenenti segreti,db_password.txt) nei sistemi di controllo versione come Git. - Ruota i segreti regolarmente. Questo minimizza la finestra di esposizione se un segreto viene compromesso.
- Concedi il minimo privilegio. Dai ai servizi accesso solo ai segreti di cui hanno assolutamente bisogno.
- Evita di registrare i valori dei segreti. Assicurati che la tua applicazione e l'infrastruttura di logging non stampino i contenuti dei segreti.
- Per distribuzioni su larga scala di livello enterprise, considera soluzioni di gestione dei segreti dedicate come HashiCorp Vault, AWS Secrets Manager o Azure Key Vault, che offrono funzionalità più avanzate come audit, generazione dinamica di segreti e integrazione con Identity and Access Management (IAM).
Una Regola Pratica per Decidere Cosa Va Dove
Prima di aggiungere un valore a environment, chiediti cosa succederebbe se un collega lo incollasse in un ticket di supporto. Se la risposta è "niente di grave", probabilmente è configurazione. Se la risposta è "dovremmo ruotare le credenziali", è un segreto.
Buone variabili d'ambiente:
APP_ENV=production
LOG_LEVEL=info
PUBLIC_BASE_URL=https://example.com
FEATURE_SIGNUP_ENABLED=false
REDIS_HOST=redis
Cattive variabili d'ambiente:
DATABASE_PASSWORD=...
STRIPE_SECRET_KEY=...
JWT_SIGNING_KEY=...
AWS_SECRET_ACCESS_KEY=...
PRIVATE_SSH_KEY=...
Ci sono aree grigie. Un hostname di database è solitamente configurazione. Un URL completo del database che include nome utente e password è un segreto. Una chiave di analisi pubblica può essere sicura per un'applicazione browser, mentre un token API privato per lo stesso fornitore non lo è. In caso di dubbio, tratta il valore come sensibile finché non provi il contrario.
I File .env di Compose Sono Facili da Fraintendere
Docker Compose usa .env in due modi diversi che le persone spesso confondono.
In primo luogo, Compose legge un file .env a livello di progetto per la sostituzione delle variabili all'interno di compose.yml:
services:
web:
image: "${APP_IMAGE}"
ports:
- "${HOST_PORT}:8080"
In secondo luogo, env_file passa le variabili nel container:
services:
web:
image: my-app
env_file:
- ./app.env
Questi file possono sembrare simili, ma servono a scopi diversi. Il primo aiuta Compose a rendere la configurazione. Il secondo diventa ambiente runtime all'interno del container. Non dare per scontato che un valore nel .env del progetto appaia automaticamente all'interno del container a meno che non lo passi esplicitamente.
Per lo sviluppo locale, un file di esempio committato è utile:
# .env.example
APP_ENV=development
LOG_LEVEL=debug
PUBLIC_BASE_URL=http://localhost:3000
Poi tieni il vero .env fuori da Git:
.env
*.env.local
secrets/
Il file di esempio documenta cosa si aspetta l'app senza esporre valori privati.
Leggere i Segreti Basati su File in un'Applicazione
Molte applicazioni si aspettano già segreti nelle variabili d'ambiente. Passare ai segreti basati su file è più facile se supporti entrambi i pattern per un po'.
Ad esempio, un 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");
Quindi il tuo file Compose può puntare a un file segreto montato:
services:
web:
image: my-app
environment:
DATABASE_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Questo pattern funziona bene perché l'applicazione può ancora funzionare in ambienti più vecchi mentre sposti la produzione verso segreti montati su file. Molte immagini ufficiali supportano già variabili che terminano con _FILE per questo motivo.
Non Mettere Segreti Negli Argomenti di Build Nemmeno
Le variabili d'ambiente non sono l'unica trappola. Anche gli argomenti di build possono perdere informazioni se li usi per recuperare pacchetti privati o clonare repository:
ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
Anche se il container finale non mostra NPM_TOKEN, la cronologia di build e i layer intermedi potrebbero esporre più di quanto ti aspetti. Con BuildKit, usa mount segreti per i segreti in fase di 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
Costruiscilo in questo modo:
docker build \
--secret id=npm_token,src=.npm-token \
-t my-app .
Questo mantiene il token fuori dal Dockerfile ed evita di incorporarlo in un layer normale. Devi ancora proteggere il file .npm-token locale e l'archivio dei segreti CI.
Kubernetes, Cloud Secret Managers e Docker
Docker Secrets sono utili in Swarm, e i segreti di Compose sono utili per configurazioni locali o su host singolo. In Kubernetes, normalmente userai Kubernetes Secrets, un operatore di segreti esterno o un'integrazione con un cloud secret manager. Su AWS, i team usano spesso AWS Secrets Manager o Systems Manager Parameter Store. Su Azure, Azure Key Vault è comune. Su Google Cloud, Secret Manager svolge lo stesso ruolo.
Il principio è lo stesso su tutte le piattaforme:
- Archivia i valori sensibili in un sistema progettato per i segreti.
- Concedi all'identità runtime l'accesso solo ai segreti di cui ha bisogno.
- Monta o inietta i segreti in fase di esecuzione.
- Ruota i segreti senza ricostruire l'immagine.
- Mantieni i segreti fuori dal controllo versione, dai layer dell'immagine, dai log e dai dashboard.
I Kubernetes Secrets sono codificati per impostazione predefinita, non automaticamente crittografati in ogni configurazione di cluster. Molti cluster gestiti supportano la crittografia a riposo, ma verifica le impostazioni effettive del cluster invece di dare per scontato. Per credenziali ad alto rischio, usa un cloud secret manager o uno strumento dedicato con log di audit e supporto per la rotazione.
La Rotazione Fa Parte del Progetto
Una strategia per i segreti che non può ruotare è incompleta. Fai queste domande prima della produzione:
- Possiamo cambiare la password del database senza ricostruire l'immagine?
- Due credenziali valide possono sovrapporsi durante un rollout?
- L'applicazione rilegge i segreti o ha bisogno di un riavvio?
- Dove vengono registrate, memorizzate nella cache o archiviate le vecchie credenziali?
- Chi viene notificato quando un segreto cambia?
Per i database, la rotazione spesso significa creare una seconda credenziale, distribuire l'applicazione con la nuova credenziale, verificare il traffico, quindi revocare quella vecchia. Per le chiavi API, dipende dal fornitore. Alcuni servizi permettono più chiavi attive; altri forzano un cutover. Progetta il tuo processo di distribuzione attorno alla dipendenza meno flessibile.
Pulisci l'Esposizione Accidentale
Se un segreto è già stato committato in Git o incorporato in un'immagine, cancellare la riga non è sufficiente. Trattalo come esposto.
La risposta usuale è:
- Revoca o ruota la credenziale.
- Rimuovila dal codice o dall'immagine corrente.
- Controlla i log CI, i registry di immagini, i tracker dei problemi e i messaggi chat per copie.
- Riscrivi la cronologia Git solo se la tua organizzazione è preparata a gestire il coordinamento; la rotazione è ancora necessaria.
- Aggiungi scansione o controlli pre-commit per ridurre errori ripetuti.
Gli strumenti possono aiutare, ma non sostituiscono le buone abitudini. Tieni i file segreti nominati chiaramente, ignorali in Git ed evita di stampare oggetti di configurazione in blocco all'avvio.
Il Pattern Funzionante
Usa le variabili d'ambiente per valori che descrivono come l'app dovrebbe funzionare in questo ambiente: porte, livelli di log, feature flag, hostname di servizi e URL non sensibili. Usa i segreti per valori che provano l'identità o concedono l'accesso: password, token, chiavi di firma, chiavi private e credenziali del fornitore.
L'immagine Docker pulita è la stessa in tutti gli ambienti. Sviluppo, staging e produzione cambiano comportamento in fase di esecuzione. La configurazione può viaggiare come variabili d'ambiente. I segreti dovrebbero provenire da un archivio di segreti o da un file segreto montato con accesso limitato. Questa separazione mantiene le distribuzioni flessibili senza trasformare ogni ispezione del container, riga di log o layer dell'immagine in una perdita di credenziali.