Un'applicazione reale raramente è un solo container. Anche un progetto modesto ha di solito un'applicazione web, un database, magari una cache e un processo che gestisce i lavori in background. Avviare tutto a mano con lunghi comandi docker run, ricordandosi porte, reti e volumi, è scomodo e soggetto a errori. Docker Compose permette di descrivere l'intero insieme in un file e di gestirlo con pochi comandi.
L'idea di fondo
Compose legge un file chiamato di solito compose.yaml (o il vecchio docker-compose.yml) in cui ogni servizio corrisponde a un container, con la sua immagine, le sue porte, i suoi volumi e le sue variabili. Il file diventa la documentazione eseguibile dell'architettura: chi lo legge capisce subito di cosa è fatto il progetto.
Un esempio concreto per un'applicazione con database PostgreSQL e cache Redis:
services:
web:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
REDIS_URL: redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 10
cache:
image: redis:7
volumes:
dbdata:
Con docker compose up -d si avvia tutto, con docker compose down si ferma, con docker compose logs -f web si seguono i log di un singolo servizio.
Reti: i servizi si chiamano per nome
Compose crea automaticamente una rete privata per il progetto. Ogni servizio è raggiungibile dagli altri usando il nome del servizio come nome host: l'applicazione si collega al database su db:5432, non su un indirizzo IP. È un dettaglio che semplifica molto la configurazione.
Nota anche che il database e la cache nell'esempio non pubblicano porte verso l'esterno. Solo il servizio web espone la 8000. È una buona abitudine: un database raggiungibile da Internet è uno dei problemi di sicurezza più frequenti, e qui è evitato semplicemente non dichiarando la porta.
Volumi: dove vivono i dati
Il volume dbdata conserva i dati di PostgreSQL anche quando il container viene ricreato. Esistono due modi principali di montare dati:
| Tipo | Esempio | Uso tipico |
|---|---|---|
| Volume con nome | dbdata:/var/lib/postgresql/data |
Dati di database e servizi, gestiti da Docker |
| Bind mount | ./src:/app/src |
Codice sorgente in sviluppo, file di configurazione |
Il bind mount è comodo in sviluppo perché le modifiche ai file sul tuo computer si riflettono subito nel container. In produzione è preferibile il volume con nome, più indipendente dalla struttura delle cartelle dell'host.
Attenzione a un comando: docker compose down -v rimuove anche i volumi, quindi cancella i dati. È utile per ripartire da zero in sviluppo, pericoloso su un server.
Variabili e segreti
Compose legge automaticamente un file .env nella stessa cartella e sostituisce le variabili come ${DB_PASSWORD}. Questo permette di tenere il file compose.yaml nel repository e le password fuori. Il file .env va escluso dal controllo di versione; al suo posto si può versionare un .env.example con valori fittizi, che spiega quali variabili servono.
Per informazioni più delicate Compose supporta anche i secrets, montati come file in sola lettura dentro il container: un approccio più pulito delle variabili d'ambiente, che possono finire nei log o nei messaggi di errore.
Dipendenze e ordine di avvio
L'opzione depends_on stabilisce l'ordine di avvio, ma di per sé non garantisce che il servizio sia pronto. Un database può impiegare alcuni secondi prima di accettare connessioni. Combinando depends_on con un healthcheck e la condizione service_healthy, come nell'esempio, l'applicazione parte solo quando il database risponde davvero.
Resta comunque buona pratica che l'applicazione sappia ritentare la connessione: in produzione il database può riavviarsi anche mentre tutto il resto è in funzione.
Più file per più ambienti
Spesso sviluppo e produzione differiscono in pochi dettagli: in sviluppo si montano i sorgenti e si attiva il debug, in produzione no. Compose permette di sovrapporre file: un compose.yaml di base e un compose.override.yaml caricato automaticamente in sviluppo, oppure un file specifico indicato esplicitamente con l'opzione -f. Così la parte comune resta in un solo posto.
I profili sono un'altra opzione: un servizio marcato con un profilo, come uno strumento di amministrazione del database, parte solo se richiesto esplicitamente.
Compose in produzione: sì, con giudizio
Compose non è solo uno strumento di sviluppo. Su un singolo server, per un'applicazione di dimensioni contenute, è una soluzione semplice e affidabile. Con qualche accortezza:
- imposta
restart: unless-stoppedperché i container ripartano dopo un riavvio del server; - definisci limiti di memoria per evitare che un servizio fuori controllo blocchi la macchina;
- configura la rotazione dei log, altrimenti i file di log dei container possono riempire il disco;
- pianifica i backup dei volumi, che Compose non fa da solo.
Quando servono più server, failover automatico o scalabilità dinamica, è il momento di valutare un orchestratore vero. Ma molti progetti non arrivano mai a quel punto, e un file Compose ben scritto li serve per anni.
In sintesi
- Docker Compose descrive in un file YAML tutti i container di un progetto e li gestisce insieme.
- I servizi comunicano su una rete privata usando il proprio nome; esponi solo le porte necessarie.
- I dati vanno sui volumi;
down -vli cancella, quindi va usato con cautela. - Usa
.envper le variabili e healthcheck per avviare i servizi solo quando le dipendenze sono pronte. - Su un singolo server Compose è adatto anche alla produzione, con riavvio automatico, limiti e backup.