☁ Cloud e DevOps

Docker Compose: gestire più container con un solo file

Come usare Docker Compose per descrivere applicazione, database e servizi accessori in un file YAML: reti, volumi, variabili e uso in produzione.

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-stopped perché 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 -v li cancella, quindi va usato con cautela.
  • Usa .env per 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.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica e in psicologia, unisce l'analisi tecnica alla comprensione del fattore umano.

Leggi anche