© Copyright 2026 Ferrantino Francesco

Docker Compose è uno strumento che ti permette di far partire più container insieme come se fossero un’unica applicazione.




Invece di lanciare ogni container manualmente con numerosi comandi separati è possibile descrivere l’intero ambiente all’interno di un unico file di configurazione chiamato compose.yml.

In questo file vengono definiti tutti i componenti necessari: quali servizi devono partire, quali immagini utilizzare, quali porte esporre, quali volumi montare e in che modo i container devono comunicare tra loro.

Una volta scritta la configurazione è sufficiente un solo comando docker compose up oppure podman-compose up per avviare automaticamente l’intero stack già funzionante e correttamente collegato.

Il principale vantaggio di Compose è la semplicità con cui consente di lavorare con applicazioni composte da più servizi, ad esempio un web server, un backend applicativo e un database. Tutta la configurazione rimane concentrata in un unico file leggibile, versionabile e facilmente condivisibile, permettendo a chiunque di ricreare lo stesso ambiente in pochi secondi.

Questo approccio è ideale per lo sviluppo locale e per i laboratori di test, perché consente di avviare, fermare e ricreare l’intero progetto in modo rapido e ripetibile, utilizzando sempre la stessa configurazione sia con Docker che con Podman.


Simulazione di una cache con PostgreSQL e Redis

Simuliamo una cache manualmente usando PostgreSQL come database principale e Redis come cache veloce. PostgreSQL è il database che conserva i dati in modo affidabile su disco Redis invece viene usato come cache in memoria veloce e temporanea per alleggerire le letture.

Prima proviamo a leggere un dato da Redis (cache) e se non c’è lo recuperiamo da PostgreSQL e lo salviamo in Redis con un TTL, infine, quando aggiorniamo il dato su PostgreSQL invalidiamo la cache per evitare valori vecchi.



STEP 1. Creazione del file docker-compose.yml ed avvio dei container

frank@debian:~$ nano compose.yml
name: labcache

services:

  postgres:
    image: postgres:15
    container_name: pg01
    environment:
      POSTGRES_PASSWORD: test123
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks:
      - backend_net

  redis:
    image: redis:7
    container_name: redis01
    ports:
      - "6379:6379"
    networks:
      - backend_net

volumes:
  pg_data:
    name: pg_data_custom

networks:
  backend_net:
    name: backend_network_custom



  
frank@debian:~$ docker compose up -d
[+] up 32/32
 ✔ Image postgres:15              Pulled                                    94.2s
 ✔ Image redis:7                  Pulled                                    45.4s
 ✔ Network backend_network_custom Created                                   0.2s
 ✔ Volume pg_data_custom          Created                                   0.0s
 ✔ Container pg01                 Created                                   4.3s
 ✔ Container redis01              Created                                   4.3s



  
frank@debian:~$ docker ps
CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                                         NAMES
a181ff53d06c   postgres:15   "docker-entrypoint.s…"   47 seconds ago   Up 43 seconds   0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp   pg01
57ed3f2b04bd   redis:7       "docker-entrypoint.s…"   47 seconds ago   Up 43 seconds   0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp   redis01



  
frank@debian:~$ docker network ls
NETWORK ID     NAME                     DRIVER    SCOPE
1788a93e0136   backend_network_custom   bridge    local
117eb0fc129d   bridge                   bridge    local
a8820964de30   host                     host      local
597294692ce2   none                     null      local



  
frank@debian:~$ docker volume ls
DRIVER    VOLUME NAME
local     3e3a65363d30f36f1aaea5629d59a030c2cb61754859047cd87da025b41bc268
local     pg_data_custom

Container e persistenza dei volumi varie opzioni:
$ docker compose up -d          # avvia senza toccare i dati
$ docker compose down           # rimuove container ma lascia i dati
$ docker compose down -v        # rimuove container e volumi (dati persi)
$ docker rm -f pg01             # elimina container singolo (dati persi se senza volume)


  
  
frank@debian:~$ docker volume inspect pg_data_custom
[
    {
        "CreatedAt": "2026-02-15T15:59:01+01:00",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.config-hash": "6f734250da016b332d779ab2e3c27a15a4320013dd1f99337a66b8a9a6b432e8",
            "com.docker.compose.project": "labcache",
            "com.docker.compose.version": "5.0.2",
            "com.docker.compose.volume": "pg_data"
        },
        "Mountpoint": "/var/lib/docker/volumes/pg_data_custom/_data",
        "Name": "pg_data_custom",
        "Options": null,
        "Scope": "local"
    }
]



  
frank@debian:~$ docker network inspect backend_network_custom
[
    {
        "Name": "backend_network_custom",
        "Id": "1788a93e0136fcda442a2465b6227a7221016e5499c3c4bec376abd8674ee08b",
        "Created": "2026-02-15T15:59:01.614981239+01:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv4": true,
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Options": {},
        "Labels": {
            "com.docker.compose.config-hash": "1c9b6b634dd19c526b592e11aebca8aa53b756d10a21750d259b92025705d61b",
            "com.docker.compose.network": "backend_net",
            "com.docker.compose.project": "labcache",
            "com.docker.compose.version": "5.0.2"
        },
        "Containers": {
            "57ed3f2b04bd1d6359e63a09da285ad242b66a8f2f5b961c9ab298e3352641e3": {
                "Name": "redis01",
                "EndpointID": "a89c39d80cf7aba5c4a162db7b13ad1aec4d6738be1e8bd925415fc7ce4187d8",
                "MacAddress": "fa:58:43:16:7d:cc",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "a181ff53d06c52794607ec6849a9e3756adbb5a3b1c03278b04c660b46b1c772": {
                "Name": "pg01",
                "EndpointID": "802d069573e43a5bfe0e5c29233a48bef3f35a92f9156cb98055f92232e9e852",
                "MacAddress": "92:93:51:0a:c8:d2",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Status": {
            "IPAM": {
                "Subnets": {
                    "172.18.0.0/16": {
                        "IPsInUse": 5,
                        "DynamicIPsAvailable": 65531
                    }
                }
            }
        }
    }
]

STEP 2. La scrittura dei dati avviene prima su PostgreSQL

Inseriamo un dato in PostgreSQL entrando nel database:
frank@debian:~$ docker exec -it pg01 psql -U postgres
psql (15.16 (Debian 15.16-1.pgdg13+1))
Type "help" for help.

postgres=# CREATE TABLE IF NOT EXISTS utenti (id SERIAL PRIMARY KEY, nome TEXT);

postgres=# INSERT INTO utenti (nome) VALUES ('Frank');

postgres=# SELECT * FROM utenti;

postgres=# \q

STEP 3. Simuliamo una lettura con cache

Entro nella console interattiva di Redis:
frank@debian:~$ docker exec -it redis01 redis-cli
Legge il valore salvato dentro Redis usando la chiave utente:1
127.0.0.1:6379> GET utente:1
(nil)

127.0.0.1:6379> QUIT
Vuol dire che in Redis la chiave utente:1 non esiste o è scaduta se avevi messo un TTL. Se la chiave non esiste recuperiamo il dato dal database:
frank@debian:~$ docker exec -it pg01 psql -U postgres -t -c "SELECT nome FROM utenti WHERE id=1;"
 Frank

STEP 4. Salvataggio in Redis con TTL

Entro nella console interattiva di Redis e salvo il risultato in Redis con TTL di 60 secondi:
frank@debian:~$ docker exec -it redis01 redis-cli
127.0.0.1:6379> SETEX utente:1 60 "Frank"
OK

Verifichiamo che ora la chiave esista:
127.0.0.1:6379> GET utente:1
"Frank"

Controlliamo il TTL (secondi rimanenti) restituisce il Time To Live in secondi:
127.0.0.1:6379> TTL utente:1
(integer) 42

(integer) 42 → la chiave scadrà tra 42 secondi
(integer) -1 → la chiave esiste ma NON ha scadenza
(integer) -2 → la chiave non esiste

Usciamo da Redis:
127.0.0.1:6379> QUIT

Ora il dato è in cache: le prossime letture verranno soddisfatte direttamente da Redis finché il TTL non scade.


STEP 5. Aggiornamento su PostgreSQL e invalidazione della cache

Se modifico il nome utente la scrittura deve avvenire prima su PostgreSQL:
frank@debian:~$ docker exec -it pg01 psql -U postgres -c "UPDATE utenti SET nome='Francesco' WHERE id=1;"
UPDATE 1
Dopo l’aggiornamento bisogna invalidare la cache:
frank@debian:~$ docker exec -it redis01 redis-cli

127.0.0.1:6379> DEL utente:1
(integer) 1

(integer) 1 = la chiave esisteva ed è stata eliminata
(integer) 0 = la chiave non esisteva
  
127.0.0.1:6379> QUIT

Alla prossima lettura la cache verrà ricreata recuperando il valore aggiornato da PostgreSQL.


STEP 6. Salvataggio del nuovo dato in Redis con TTL

Entro nella console interattiva di Redis e salvo il risultato in Redis con TTL di 60 secondi:
frank@debian:~$ docker exec -it redis01 redis-cli
127.0.0.1:6379> SETEX utente:1 60 "Francesco"
OK

Verifichiamo che ora la chiave esista:
127.0.0.1:6379> GET utente:1
"Francesco"

Controlliamo il TTL (secondi rimanenti). Controlliamo il TTL (secondi rimanenti) restituisce il Time To Live in secondi:
127.0.0.1:6379> TTL utente:1
(integer) 40

(integer) 40 → la chiave scadrà tra 40 secondi
(integer) -1 → la chiave esiste ma NON ha scadenza
(integer) -2 → la chiave non esiste

Usciamo da Redis:
127.0.0.1:6379> QUIT

Ora il dato è in cache e le prossime letture verranno soddisfatte direttamente da Redis finché il TTL non scade.

Ferrantino Francesco

Ferrantino Francesco

Cookie Policy

Leggi  informativa Cookie Policy
Il "Sito" utilizza i Cookie per rendere i propri servizi semplici e efficienti per l’utenza che visiona le pagine di franksoft.it

Disclaimer

L'autore degli articoli non si assume nessuna responsabilità per eventuali danni ai vostri dispositivi. Tutto ciò che viene spiegato è puramente a scopo dimostrativo.
Il presente sito non costituisce testata giornalistica in quanto non ha carattere periodico ed è aggiornato secondo la mia disponibilità e la reperibilità dei materiali ivi contenuti. Pertanto, non può essere considerato in alcun modo un prodotto editoriale ai sensi della Legge n. 62 del 7/03/2001.

Licenza

Tutte le immagini presenti nel sito appartengono ai rispettivi titolari e sono utilizzate senza alcuno scopo di lucro. Ogni eventuale violazione del copyright non è intenzionale.
Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Non commerciale - Non opere derivate 4.0 Internazionale.  Licenza Creative Commons