SSD Nodes Learn 8GB di RAM — $66/anno
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-02

Docker Compose: bind mount o volume denominato?

Confronta bind mount e volumi denominati in Docker Compose: scopri cosa usare per config e dati, gli errori di permessi e come ispezionare, salvare e migrare.

Bind mount o volume denominato: la risposta breve

I volumi di Docker Compose sono di due tipi e la scelta dipende da chi gestisce i file. Usa un bind mount per i file che scrivi e leggi direttamente, ad esempio configurazioni, template e siti statici. Usa un volume denominato per i dati gestiti dall'applicazione, ad esempio file di database, indici di ricerca e contenuti multimediali caricati. Un bind mount punta a un percorso sull'host che puoi aprire in un editor. Un volume denominato è uno spazio di archiviazione creato e gestito da Docker, a cui accedi tramite Docker.

Entrambi vengono specificati con la stessa chiave volumes: all'interno di un servizio, per questo vengono spesso confusi. La differenza è nella parte a sinistra dei due punti. Una parte sinistra che inizia con . o / è un percorso dell'host, quindi indica un bind mount. Qualsiasi altro valore è un nome, quindi indica un volume denominato; questo nome deve essere dichiarato anche nel blocco volumes: di livello superiore.

Le due sintassi in un file Compose

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data è un volume denominato. ./nginx.conf:/etc/nginx/nginx.conf è un bind mount e :ro lo monta in sola lettura, che è l'impostazione predefinita corretta per una configurazione che un container non dovrebbe mai riscrivere. Se si omette la voce di primo livello volumes:, Compose si arresta con service "db" refers to undefined volume pgdata.

Avvia i servizi ed elenca ciò che Docker ha creato:

docker compose up -d
docker volume ls

Il volume non si chiama pgdata. Si chiama <project>_pgdata, perché per impostazione predefinita il nome del progetto corrisponde al nome della directory che contiene il file Compose. Una directory chiamata myapp produce myapp_pgdata. Questo è importante perché rinominando la directory si ottiene un nuovo volume vuoto e l'applicazione sembra aver perso i dati. Non è così: il vecchio volume è ancora elencato da docker volume ls. Fissa il nome con name: nel file Compose oppure imposta COMPOSE_PROJECT_NAME se la directory potrebbe essere spostata. Impostazioni di questo tipo vanno mantenute insieme agli altri file di ambiente e segreti di Compose.

Perché gli errori di autorizzazione riguardano solo i bind mount

Questa è la differenza pratica principale e deriva da una sola regola: un volume denominato vuoto al primo utilizzo viene inizializzato dall'immagine, mentre un bind mount non viene mai inizializzato in questo modo.

Quando Docker monta un volume denominato vuoto sopra una directory che nell'immagine contiene già dei dati, copia quei dati nel volume, mantenendo la proprietà e i permessi impostati dall'immagine. L'immagine ufficiale di Postgres include /var/lib/postgresql/data, di proprietà dell'utente postgres, quindi il volume risulta di proprietà dello stesso ID numerico e il database si avvia.

Un bind mount si comporta in modo opposto. Il container vede ciò che si trova sull'host, inclusa la proprietà, mentre il contenuto dell'immagine presente in quel percorso viene nascosto. Se la directory sull'host non esiste, il Docker daemon la crea e il daemon viene eseguito come root; di conseguenza, la directory risulta di proprietà di root:root. Un processo del container eseguito da un utente non root non può quindi scriverci:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

La soluzione consiste nel far coincidere i numeri. La proprietà attraverso un bind mount viene confrontata tramite l'ID numerico dell'utente, non tramite il nome, perché il container ha il proprio /etc/passwd. Un utente chiamato app nel container non ha alcun significato sull'host. Uid 1000 significa uid 1000 su entrambi i lati.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id stampa l'uid con cui viene effettivamente eseguito il processo del container. Imposta la directory sull'host in modo che corrisponda a quel numero oppure vincola il container al tuo numero con user: "1000:1000" nel servizio. Impostare user: è più pulito per un'applicazione sviluppata internamente. Modificare la proprietà della directory sull'host è più sicuro per un'immagine che non hai sviluppato, perché alcune immagini avviano un entrypoint come root, riducono i privilegi e si aspettano proprietà specifiche nelle directory sottostanti.

È utile conoscere altri due casi problematici. Su Fedora, RHEL e altri sistemi con SELinux (Security-Enhanced Linux) in modalità enforcing, un bind mount viene negato finché non viene rietichettato; aggiungi quindi :z per un percorso condiviso tra container oppure :Z per un percorso utilizzato da un solo container, nella forma - ./data:/data:Z. Inoltre, un bind mount di un singolo file, invece che di una directory, smette di funzionare quando un editor sostituisce il file anziché scriverci direttamente, perché il mount segue l'inode originale. Il container continua a vedere il contenuto precedente finché non lo riavvii. Monta la directory padre quando il file viene modificato spesso.

Prestazioni: dove la differenza è reale

Su un server Linux, entrambi i tipi usano lo stesso percorso del kernel. La differenza di throughput è quindi abbastanza ridotta da non dover scegliere in base a questo criterio. I volumi denominati che usano il driver predefinito local risiedono nello stesso filesystem del resto di Docker, in /var/lib/docker/volumes/. Un bind mount risiede invece nel percorso specificato.

La differenza emerge in Docker Desktop per macOS e Windows, dove i container vengono eseguiti all'interno di una macchina virtuale. In questo caso, un bind mount attraversa il filesystem dell'host fino alla macchina virtuale tramite un livello di condivisione dei file. I carichi di lavoro con molte operazioni su file di piccole dimensioni, come un albero di dipendenze Node.js o la cache di un framework PHP, rallentano sensibilmente. I volumi denominati restano all'interno della macchina virtuale e non sostengono questo costo. Per questo molti file Compose usati nello sviluppo eseguono il bind mount della directory del codice sorgente, ma dichiarano un volume denominato su node_modules.

L'altra differenza reale riguarda la posizione dei dati. Un bind mount su /mnt/backup salva i dati su quel disco. Un volume denominato salva i dati nel filesystem che contiene /var/lib/docker, che su un VPS è generalmente il disco root. Un database che cresce all'interno di un volume denominato riempie lo stesso disco su cui si trovano i log di sistema. Verifica lo spazio prima che si verifichi un incidente:

docker system df -v
df -h /var/lib/docker

docker system df -v elenca tutti i volumi con le relative dimensioni e contrassegna quelli a cui non fa più riferimento alcun container.

Ispezionare un volume denominato

Un volume denominato non è una scatola nera. Chiedi a Docker dove si trova:

docker volume inspect myapp_pgdata

Il campo Mountpoint indica un percorso reale sull'host, normalmente /var/lib/docker/volumes/myapp_pgdata/_data. Puoi leggerlo con sudo ls, ed è utile per un controllo rapido. Non usarlo come posizione in cui modificare i file. La scrittura in quel percorso come root riproduce il problema di proprietà descritto sopra, e il percorso è un dettaglio del driver local che gli altri driver di volume non condividono.

Il modo sicuro per esaminare il contenuto consiste nell'usare un container temporaneo che monta il volume:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

Funziona con qualsiasi driver, mostra le stesse autorizzazioni visibili al container reale e non lascia nulla dietro di sé grazie a --rm.

Backup per ciascun tipo

Un bind mount è una directory ordinaria, quindi qualsiasi strumento di backup a livello di file lo gestisce già. Indicate come destinazione del backup il percorso sull'host e avete terminato. Un volume denominato richiede un passaggio aggiuntivo, perché lo strumento deve accedervi. Montate il volume e una directory dell'host nello stesso container temporaneo, quindi create un archivio:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

Ripristinate invertendo l'operazione in un volume vuoto:

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

tar conserva la proprietà numerica quando viene eseguito come root all'interno del container. Questo consente all'applicazione di utilizzare il volume ripristinato.

Un'avvertenza riguarda entrambi i tipi. Copiare i file di un database mentre il database è in esecuzione crea un archivio di dati in continua modifica e il ripristino può produrre uno stato danneggiato. Arrestate prima il servizio oppure eseguite un dump tramite lo strumento del database, come in docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. In questo modo ottenete un file semplice, che potete poi includere in una normale routine di backup restic crittografata insieme ai file compose.

Migrazione di un bind mount a un volume denominato

Lo spostamento è una copia, non una ridenominazione, e richiede circa un minuto.

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a mantiene proprietario, permessi e timestamp, quindi l'utente del container che poteva leggere la directory precedente può ancora leggere il nuovo volume. Modifica quindi il servizio in modo che utilizzi pgdata:/var/lib/postgresql/data, aggiungi pgdata al blocco volumes: di primo livello, esegui docker compose up -d e controlla i log dell'applicazione prima di eliminare la directory precedente. Per eseguire l'operazione inversa, usa lo stesso comando scambiando /from e /to.

Durante i test, ricorda un aspetto. docker compose down lascia invariati i volumi denominati, mentre docker compose down -v elimina ogni volume denominato dichiarato dal progetto e non è possibile annullare l'operazione. Un bind mount rimane disponibile in entrambi i casi, perché Docker non ha mai gestito quella directory. Se i comandi del ciclo di vita non ti sono ancora familiari, la guida di base a Docker Compose per un VPS li illustra passo per passo.

Scelta, servizio per servizio

Chiedi quale componente scrive il file. La configurazione che modifichi in un editor di testo e sottoponi a commit in git appartiene a un bind mount, montato su :ro, perché vuoi che sia visibile e sottoposta al controllo versione. Lo stato dell'applicazione che non apri mai manualmente appartiene a un volume denominato, perché Docker configura correttamente i permessi e i dati non dipendono da un percorso dell'host.

Il caso misto riguarda i contenuti multimediali. Una libreria di foto viene scritta dall'applicazione, ma viene anche gestita da te e spesso è abbastanza grande da richiedere un disco specifico. Montala tramite bind mount in un percorso su quel disco e imposta deliberatamente la proprietà una sola volta. Questo è il modello adottato dalla maggior parte degli stack self-hosted: volumi denominati per database e cache, bind mount per la configurazione e per la directory di grandi dimensioni che vuoi gestire direttamente.

FAQ

Qual è la differenza tra un bind mount e un volume denominato?

Un bind mount associa un percorso dell'host a un percorso nel container, quindi entrambi vedono la stessa directory e puoi modificarla con gli strumenti normali. Un volume denominato è uno spazio di archiviazione creato e gestito da Docker, identificato dal nome e dichiarato nel blocco di primo livello volumes:. La distinzione pratica riguarda la gestione: usa i bind mount per la configurazione che gestisci direttamente e i volumi denominati per i dati gestiti dall'applicazione.

Perché con un bind mount ricevo "permission denied", ma non con un volume denominato?

Un volume denominato vuoto viene inizializzato usando i dati dell'immagine, quindi eredita la proprietà impostata dall'immagine e l'utente del container può scrivervi. Un bind mount mostra la directory dell'host esattamente nello stato attuale. Se Docker ha dovuto creare quella directory, l'ha assegnata a root. Esegui docker compose exec <service> id per visualizzare l'ID numerico usato dal container, quindi sudo chown -R <uid>:<gid> sulla directory dell'host, oppure imposta user: "1000:1000" sul servizio.

Dove archivia Docker i volumi denominati su disco?

Con il driver predefinito local, i volumi si trovano in /var/lib/docker/volumes/<volume>/_data e docker volume inspect <volume> stampa il valore esatto di Mountpoint. Leggilo se devi verificare qualcosa, ma scrivici solo tramite un container, perché modificare i dati come root sull'host cambia la proprietà in modi che il container non si aspetta.

Come posso eseguire il backup di un volume denominato?

Esegui un container temporaneo con il volume e una directory dell'host montati entrambi, quindi crea un archivio da uno all'altra con docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Per un database, esegui un dump con lo strumento specifico del database invece di copiare file mentre il database è in uso, perché una copia eseguita durante operazioni di scrittura può essere ripristinata in uno stato corrotto.

docker compose down elimina i miei volumi?

docker compose down rimuove i container e le reti, lasciando invariati i volumi denominati. docker compose down -v elimina anche tutti i volumi denominati dichiarati dal progetto, in modo permanente. I bind mount non vengono mai rimossi da nessuno dei due comandi, perché la directory appartiene all'host e non a Docker.