Il problema che Compose risolve
Un’applicazione reale non è quasi mai fatta di un solo container. Un’API web ha bisogno di un database, il database ha bisogno di un volume perché i dati sopravvivano a un riavvio, e poi aggiungi una cache Redis e un worker in background. Sono quattro comandi docker run, ognuno con i suoi flag per porte, volumi, variabili d’ambiente e una rete condivisa. Nell’ordine giusto. Ogni volta che ti metti a lavorare.
Docker Compose sostituisce quei comandi con un file e un comando. Descrivi i container e come si collegano in un file chiamato compose.yaml, e docker compose up li avvia tutti. Se le immagini e docker run sono una novità per te, leggi prima cos’è Docker e come funzionano i container.
Cosa descrive il file compose
Un file compose ha una manciata di chiavi di primo livello. Quella che usi sempre è services — ogni servizio è un container che Compose eseguirà.
| |
Due container. api viene costruito dal Dockerfile nella directory corrente e pubblica la porta 8000. db esegue l’immagine ufficiale postgres:18 e tiene i suoi dati in un volume con nome, così i dati restano quando il container viene ricreato.
Qui il lavoro vero lo fanno due dettagli:
apiraggiungedball’hostnamedb. Compose mette ogni servizio su una rete condivisa e registra il nome di ogni servizio come nome DNS. Niente indirizzi IP, niente vecchio--link.DATABASE_URLpunta adb:5432e viene risolto.depends_onconcondition: service_healthytrattieneapifinché Postgres non risponde davvero. Undepends_onsemplice aspetta solo che il container parta, non che il processo del database al suo interno accetti connessioni: è questa differenza il motivo più comune per cui un’app dà “connection refused” al primo avvio.
Le tre chiavi principali: services, volumes, networks
Tre chiavi di primo livello corrispondono a concetti Docker che già conosci:
| Chiave | Cosa definisce | A mano si fa con |
|---|---|---|
services | i container da eseguire | docker run |
volumes | volumi con nome per i dati che devono persistere | docker volume create |
networks | reti tra i servizi | docker network create |
networks lo dichiari raramente. Compose crea una rete per progetto e ci collega ogni servizio, ed è per questo che l’esempio con Postgres non aveva bisogno di alcuna configurazione di rete. Dichiara le reti esplicitamente solo quando vuoi impedire a gruppi di servizi di raggiungersi tra loro:
| |
I comandi che userai davvero
Esegui questi dalla directory che contiene compose.yaml.
| |
up e down sono la coppia che digiti più spesso. up si può rilanciare quante volte vuoi: dopo che hai modificato il file, ricrea solo i servizi la cui configurazione è cambiata e lascia stare gli altri.
Cosa stampa docker compose up al primo avvio
Esegui l’esempio con Postgres qui sopra e ottieni più o meno questo:
| |
Ai nomi Compose antepone il nome del progetto (di default quello della directory) e aggiunge in coda un numero, perché è pronto a eseguire più di una replica di uno stesso servizio. app-db-1 risulta Healthy prima che app-api-1 parta — è condition: service_healthy che fa il suo lavoro.
Come tenere le password fuori dal file compose
Compose legge un file chiamato .env nella directory del progetto e sostituisce i riferimenti ${VAR} nel file compose:
| |
| |
Tieni .env fuori dal version control e committa invece un .env.example con valori vuoti o fittizi. Per qualcosa di davvero sensibile in un ambiente in produzione, usa i Docker secrets invece delle variabili d’ambiente.
Servizi opzionali con i profiles
Non tutti i servizi devono partire ogni volta. Una voce profiles tiene un servizio inattivo finché non lo chiedi:
| |
docker compose up avvia api e db e ignora del tutto seed. docker compose --profile tools run --rm seed esegue il seeder quando lo chiedi. Usa i profiles per tutto ciò che è una tantum: seeder, migrazioni, una shell di debug, un generatore di carico che vuoi solo durante un test.
Compose v2 vs il vecchio docker-compose
Se una guida ti dice di eseguire docker-compose con il trattino, è precedente al 2023. Quella era la v1, scritta in Python, ora a fine vita. Lo strumento attuale è docker compose come sottocomando della CLI di Docker; è incluso in Docker Desktop e nel pacchetto plugin Compose per Docker Engine.
Nei file più vecchi troverai in cima anche questa riga:
| |
La chiave version non ha alcun effetto in Compose v2. Compose valida rispetto alla Compose Specification attuale e ti avvisa quando la chiave è presente. Rimuovila e l’avviso sparisce.
Anche il nome di default del file è cambiato: compose.yaml è quello attuale, docker-compose.yml funziona ancora, e Compose cerca entrambi.
Live reload durante lo sviluppo
docker compose watch (Compose 2.22 e successivi) aggiorna i container mentre modifichi il codice. Aggiungi un blocco develop al servizio:
| |
sync copia i file modificati direttamente nel container in esecuzione, così una modifica al codice ha effetto senza un rebuild. rebuild fa scattare un rebuild completo dell’immagine quando cambia un file che influisce sulla build, come un file di lock delle dipendenze. Avvialo con docker compose watch, oppure aggiungi --watch a up.
Quando Compose è lo strumento sbagliato
Compose esegue i container su una sola macchina. È questo il confine, e gran parte di quello che sembra mancare a Compose sta dall’altra parte.
Va bene per lo sviluppo locale, i test automatici in CI e piccoli deploy su un singolo host. Non sa distribuire container su più server, sostituirne uno quando un nodo muore, fare rollout progressivi vincolati agli healthcheck, né gestire l’autoscaling.
Sono compiti di un orchestratore: Kubernetes, o Docker Swarm se vuoi qualcosa di più leggero. I due non sono in competizione. Un file compose è spesso la bozza che poi diventa un insieme di manifest Kubernetes, e parecchi team continuano a usare Compose in locale molto tempo dopo che la produzione è passata a un cluster.
Anche con Podman c’è un percorso compatibile: podman compose e podman-compose leggono lo stesso formato di file, con i distinguo trattati in Docker vs Podman.
Errori comuni
- Secret committati in
compose.yaml. Il file finisce su Git. Usa.env(in gitignore) o i Docker secrets. - Fidarsi di
depends_onper aspettare che un servizio sia pronto. Da solo aspetta che il container parta, non che il servizio accetti connessioni. Abbinalo a unhealthcheckecondition: service_healthy. - Impostare
container_name. Ti impedisce di eseguire più di una copia del progetto e rompedocker compose up --scale. Lascia che sia Compose a nominare i container. - Fare bind-mount sopra una cartella di dipendenze installate. Montare la cartella del progetto in un container Node o Python può nascondere il
node_moduleso il virtualenv creato durante la build. Monta le sottocartelle del sorgente, oppure metti un volume anonimo sul percorso delle dipendenze.
Come portare un’app esistente su Compose
Prendi l’app che oggi avvii con uno script pieno di righe docker run e spostala in un compose.yaml un servizio alla volta. Falla partire, leggi i log, sistema quello che si rompe, aggiungi il servizio successivo. Dai un healthcheck a tutto ciò da cui altri servizi dipendono: è il passaggio che di solito si salta, ed è quello che ferma il “connection refused” all’avvio a freddo.
Saprai che ha funzionato quando docker compose down -v && docker compose up ricostruisce l’intero ambiente da zero e l’app riparte sempre allo stesso modo.