Block Image

Cos'è Docker Compose

Docker Compose è un tool che permette di raggruppare e avviare un insieme di container con un solo comando.
Viene utilizzato un file YAML dove sono elencati tutti i container da creare e avviare.
In Docker Compose, ogni container viene definito come un service (da non confondere con il concetto di service di Kubernetes).

Docker Compose V2 supporta il comando docker compose, rispetto alla versione V1 dove il comando da eseguire è docker-compose. Questo perché Compose V2 è stato integrato con la Docker CLI.

Vantaggi di Docker Compose rispetto a Docker

Ci sono diversi vantaggi nell'utilizzare Docker Compose per avviare i container, invece che Docker "puro".
In primis, abbiamo un manifest dove specifichiamo tutti i container. Un file è versionabile rispetto all'esecuzione di comandi imperativi, quindi questo è il primo vantaggio. Certo, potremmo creare uno script contenente i vari comandi imperativi di Docker, ma è pur sempre un file scritto da noi e quindi soggetto a errori, rispetto a un manifest di Docker Compose.
Un altro vantaggio è che grazie a Docker Compose, possiamo specificare facilmente l'ordine di avvio dei container elencati (vedremo dopo nel dettaglio come fare).
Altro vantaggio ancora, Docker Compose crea per noi una network per i container elencati, in modo tale che possano comunicare facilmente tra loro.

Installazione di Docker Compose

Per Windows e Mac, il tool Docker Compose è già incluso in Docker Desktop. Per Linux, bisogna installare un pacchetto aggiuntivo.
Riporto qui la doc ufficiale su come installare Docker Compose: https://docs.docker.com/compose/install/.

Docker Compose in pratica

Scriveremo un manifest di Docker Compose per containerizzare una semplice applicazione Java Spring Boot, che utilizza Postgres come Datasource. Potete trovare il link del progetto qui: Spring Docker.

L'applicazione contiene semplici servizi REST CRUD per un'entità Book, utilizzando Spring Data REST.
In locale, l'applicazione utilizza H2 come Datasource. Quando conteinerizziamo l'applicazione, utilizzerà invece Postgres.

Scriviamo il file docker-compose.yml

Scriviamo il file manifest docker-compose.yml:

version: '3.8'
services:
   db:
      container_name: pg_container
      image: postgres:14
      restart: "no"
      environment:
         POSTGRES_USER: user
         POSTGRES_PASSWORD: password
         POSTGRES_DB: spring-docker
      ports:
         - "5432:5432"
      volumes:
         - pgdata:/var/lib/postgresql/data
   pgadmin:
      container_name: pgadmin4_container
      image: dpage/pgadmin4
      restart: "no"
      environment:
         PGADMIN_DEFAULT_EMAIL: admin@admin.com
         PGADMIN_DEFAULT_PASSWORD: user
      ports:
         - "5050:80"
      depends_on:
         - "db"
   spring-docker:
      container_name: spring-docker
      image: vincenzoracca/spring-docker:0.0.1-SNAPSHOT
      #    If you want to build the image from the Dockerfile, uncomment the line below.
      #    build: .
      restart: "no"
      environment:
         - BPL_JVM_THREAD_COUNT=50
         - BPL_DEBUG_ENABLED=true
         - BPL_DEBUG_PORT=9090
         - SPRING_DATASOURCE_URL=jdbc:postgresql://pg_container:5432/spring-docker
         - SPRING_DATASOURCE_USERNAME=user
         - SPRING_DATASOURCE_PASSWORD=password
      ports:
         - "8081:8081"
         - "9090:9090"
      depends_on:
         - "db"
volumes:
   pgdata:

Analizziamo il manifest:

  1. La prima riga contiene la versione del manifest Docker Compose. Differenti versioni del manifest posso avere strutture diverse del file. Qui se volete approfondire docs.docker.com/compose/compose-file/compose-versioning.

  2. Dalla seconda riga, abbiamo la sezione principale, ovvero i services, dove elenchiamo tutti i container.

  3. A ogni service viene assegnato un nome (db, pgadmin, spring-docker).

  4. Con container_name indichiamo il nome da assegnare al container creato (corrisponde alla flag --name del comando docker run).

  5. Con image, indichiamo il nome dell'immagine. Proprio come con Docker puro, se l'immagine non è presente in locale, viene scaricata di default dal Docker Hub.

  6. Opzionalmente, possiamo aggiungere il campo build per indicare il percorso di un Dockerfile. Se il Dockefile si trova nello stesso path del docker-compose.yml, possiamo scrivere un "." (punto) come valore di build. In tal caso, il nome dell'immagine creata sarà uguale al valore del campo image. Se non viene indicato il campo image, Docker Compose assegnerà automaticamente un nome all'immagine.

  7. Il campo environment serve per dichiarare delle variabili di ambiente (corrisponde alla flag --env del comando docker run). Se le stesse variabili sono dichiarate anche nel Dockerfile, viene data priorità a quelle del Docker Compose.

  8. Il campo ports corrisponde alla flag -p del comando docker run. Serve quindi per mappare le porte di localhost con quelle esposte dal container.

  9. Il campo volumes dentro la sezione di un service, serve ad assegnare al container uno o più volumi (corrisponde alla flag -v del comando docker run).

  10. Il campo volumes fuori la sezione del service (quello nella penultimate riga), serve per dichiarare dei volumi, che verranno creati da Docker Compose. Se il volume è già esistente perché creato mediante comando imperativo, deve essere aggiunto il campo external: true

volumes:
 pgdata:
  external: true
  1. Il campo depends_on, serve a indicare a Docker Compose che il container da creare deve essere avviato dopo la lista di container elencati nel depends_on. Visto che Docker Compose ragione in termini di services, dobbiamo indicare i nomi dei service relativi ai container e non direttamente i nomi dei container. Ad esempio, in questo caso i container dell'app Java e di PgAdmin saranno avviati dopo il container di Postgres.
Avviamo i container con Docker Compose

Innanzitutto, se non volete creare l'immagine dell'app Spring Boot tramite Dockerfile, potete utilizzare il comando ./mvnw spring-boot:build-image. Altrimenti, decommentate la riga dove è presente il campo build nel file docker-compose.yml.

Posizioniamoci nel path del file docker-compose.yml ed eseguiamo il comando: docker compose up

Block Image

Dall'immagine possiamo notare che vengono creati in primis una network per i container e il volume, poi i tre container ed infine quest'ultimi vengono avviati.

Con i comandi docker network ls e docker ps vediamo che effettivamente è stata creata la network e i container, che sono up & running.

Block Image

Inoltre, utilizzando il comando docker inspect sui tre container, possiamo verificare che fanno parte della stessa network, spring-docker_default.

Block Image

Possiamo spegnere i container utilizzando il comando docker compose stop.

Block Image

Al prossimo avvio con docker compose up, Docker Compose avvierà i container creati precedentemente, senza crearli nuovamente da zero. Questo vale anche quando aggiungiamo un nuovo service (container) al nostro manifest.
Docker Compose crea solo i "nuovi" container, o aggiorna i vecchi se ci sono state delle modifiche allo YAML.

Al comando docker compose up possiamo aggiungere la flag -d per avviare i container in background:
docker compose up -d.

Il comando docker compose down invece, oltre a spegnere i container del manifest, distrugge anche questi ultimi, oltre a distruggere la network (ma il volume per ovvie ragioni viene mantenuto):

Block Image

Conclusioni

In questo articolo abbiamo dato una panoramica generale di Docker Compose. Il mio consiglio è di utilizzare sempre Docker Compose quando abbiamo bisogno di gestire più container collegati tra loro.
Col manifest abbiamo a portata di mano le caratteristiche del container, come ad esempio quale immagine utilizza, i suoi volumi, etc.
Inoltre, la modifica di un container (come ad esempio aggiornare la versione della sua immagine) è molto più semplice. Basta modificare il manifest ed eseguire il solito comando docker compose up (con i comandi imperativi di Docker, dovremmo prima spegnere il container, poi distruggerlo, e infine ricrearlo con la nuova versione dell'immagine).

Progetto GitHub Docker Compose per l'app Java Spring Boot: Spring Docker.
Doc ufficiale su Docker Compose: docs.docker.com/compose.
Miei articoli su Docker: Docker

Libri consigliati su Docker e Kubernetes: