Cos'e#
Un volume Docker e uno spazio di archiviazione persistente gestito da Docker, separato dal filesystem del container. I dati in un volume sopravvivono all'eliminazione del container, al contrario di quelli scritti nel layer del container che vengono persi.
TL;DR#
# Senza volume: i dati muoiono col container
docker run postgres → container eliminato → dati persi
# Con volume: i dati persistono
docker run -v db-data:/var/lib/postgresql/data postgres
→ container eliminato → dati nel volume intatti
→ nuovo container con stesso volume → dati recuperatiUn volume e come un hard disk esterno collegato al container: puoi staccare il container, il disco resta.
Come funziona#
Docker Host (server Hetzner)
│
├── /var/lib/docker/volumes/
│ ├── n8n_data/_data/ ← dati reali qui
│ ├── n8n_postgres_data/_data/
│ └── postgres_rag_data/_data/
│
└── Container
└── /home/node/.n8n ← container vede questo path
└── (montato su n8n_data) ← ma i dati vivono nel volumeIl container legge e scrive su un path interno (es. /var/lib/postgresql/data), Docker si occupa di redirigere quel traffico verso il volume sul filesystem dell'host.
Tipi di volume#
| Named volume | Bind mount | Volume anonimo | |
|---|---|---|---|
| Persistente | si | si | si (ma perdi il riferimento) |
| Path visibile sull'host | no — vive in /var/lib/docker/volumes/ | si — e un path reale tuo | no |
| Editabile dall'host | no (serve docker exec) | si — editor, git, tutto | no |
| Portabile tra server | si | dipende dal path assoluto | no |
| Usato per | dati che possiede il container | config che controlli tu | da evitare |
| Usato per (casi reali) | DB (postgres, mysql), log applicativi, queue, stato interno generato dal container | file di config che editi spesso, certificati, .env, config che vuoi sotto git | niente — usa named volume |
Esistono tre modi per montare dati in un container:
Named volume#
gestito da Docker, nome esplicito. E il tipo consigliato per la produzione.
volumes:
- app_data:/var/lib/app/dataNaming convention Docker: {nome_cartella_progetto}_{nome_volume}
Il path /var/lib/app/data esiste SOLO dentro il container. Sull'host i dati vivono in /var/lib/docker/volumes/{progetto}_app_data/_data/. Non trovi quella cartella sul tuo host al path del container — un named volume non crea mai un path sull'host: crea storage interno a Docker e lo espone al container al path indicato.
Per modificare i file dentro un named volume devi entrare nel container con docker exec.
Bind mount#
mappa un path reale dell'host dentro il container.
volumes:
- ./config/app.conf:/etc/app/app.conf./config/app.conf e un file reale sul tuo host. Il container lo legge al path interno. Lo editi con il tuo editor sull'host senza mai aprire il container. Va sotto git, e portabile, e leggibile da fuori.
Chi possiede il dato — regola per scegliere:
| Domanda | Risposta | Usa |
|---|---|---|
| Il dato lo genera o gestisce il container? | DB, observability, stato app | Named volume |
| Il dato lo gestisci tu dall'esterno? | Config, certificati, .env | Bind mount |
| Vuoi modificare i file senza ricostruire l'immagine? | Codice sorgente in sviluppo | Bind mount |
I volumi non hanno niente a che fare con la ricostruzione dell'immagine. docker build serve solo quando cambi il Dockerfile. Con i volumi (named o bind) i dati persistono e sono accessibili senza mai ricostruire niente — la differenza e solo dove vivono i file e chi li modifica.
In sviluppo il bind mount sul codice sorgente e comune: modifichi i file sull'host, il container li vede subito senza restart.
- ./src:/app/src # sviluppo: live reload senza rebuildIn produzione il bind mount si usa solo per file di configurazione che vuoi gestire dall'host — non per il codice, che e dentro l'immagine.
La differenza visiva:
Host filesystem Container filesystem
/var/lib/docker/volumes/
myproject_app_data/_data/ ←─named volume─→ /var/lib/app/data/
file.db file.db ← vedi qui
logs/ logs/
(modifichi con docker exec)
./config/
app.conf ←─bind mount────→ /etc/app/app.conf
(modifichi sull'host) (stessa cosa, path diverso)Se un file e in un named volume e non lo vedi sull'host al path del container, non e un bug — e il comportamento corretto. Cercalo in /var/lib/docker/volumes/ oppure entra nel container.
Volume anonimo#
nome generato da Docker (hash). Da evitare in produzione perche non e identificabile.
volumes:
- /var/lib/postgresql/data # no nome = anonimoSintassi docker run vs docker-compose#
La sintassi -v in docker run e' equivalente alla dichiarazione in compose, ma inline — non esiste una sezione volumes: separata.
# Bind mount — path assoluto host:path container
docker run -v /home/user/config:/etc/app/config image
# Bind mount — path relativo con $(pwd)
docker run -v "$(pwd):/home/claude/workspace" image
# Named volume — nome:path container
docker run -v mydata:/var/lib/data image
# Piu' volumi insieme
docker run \
-v "$(pwd):/home/claude/workspace" \
-v "$HOME/.myapp:/home/claude/.myapp" \
-v mydata:/var/lib/data \
imageCon docker run i named volume vengono creati automaticamente se non esistono. Non hanno il prefisso del progetto come in compose — il nome e' esattamente quello dichiarato.
La stessa logica bind mount / named volume si applica in entrambi i contesti — cambia solo la sintassi di dichiarazione.
Naming e prefissi#
Docker compone il nome fisico del volume unendo il nome del progetto e il nome dichiarato nel compose. Il nome del progetto corrisponde alla cartella che contiene il docker-compose.yml.
cartella del progetto: myproject/
volume dichiarato nel compose: app_data
nome fisico su Docker: myproject_app_data
→ visibile in: /var/lib/docker/volumes/myproject_app_data/
→ listabile con: docker volume ls | grep myprojectSe non conosci il nome esatto, ricavalo dalla cartella del progetto + il nome nel compose:
# Struttura compose
volumes:
- app_data:/var/lib/app # nome dichiarato: app_data
# Se il progetto si chiama "single-node":
docker volume ls | grep single-node
# → single-node_app_dataPer rendere il nome indipendente dalla cartella, si usa il campo name: nella sezione volumes:
volumes:
db-data: # nome interno al compose (come una variabile)
name: n8n_postgres_data # nome fisico su Docker (stabile)Questo e il pattern corretto per la produzione: il nome fisico non cambia mai, anche se si sposta la cartella del progetto.
External vs owned#
Un volume puo essere di proprieta del compose o dichiarato come esterno:
# Compose CREA il volume (comportamento di default)
volumes:
db-data:
name: n8n_postgres_data
# Compose USA un volume gia esistente (non lo crea)
volumes:
db-data:
external: true
name: n8n_postgres_dataexternal: true e utile quando il volume e stato creato manualmente o da un altro compose. Il rischio e che in una migrazione su un nuovo server il volume non esista e il compose fallisca all'avvio.
Collegato a#
- system — categoria
- docker-volume — comandi: ls, inspect, migrazione, restore
- docker-network-defense — reti Docker, stesso tipo di ragionamento su external/owned
- docker-compose — dove i volumi vengono dichiarati e usati


