SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Docker Compose: bind mount o volume denominato?

Scopri quando usare bind mount o named volume in Docker Compose, evita problemi di permessi e impara a ispezionare, fare backup e migrare i dati.

Bind mount o named volume: risposta breve

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

Entrambi vengono definiti con la stessa chiave volumes: all'interno di un servizio, per questo vengono spesso confusi. La differenza è il lato sinistro dei due punti. Se il lato sinistro inizia con . o /, indica un percorso sull'host, quindi si tratta di un bind mount. Qualsiasi altro valore è un nome, quindi si tratta di un named volume; 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: è l'impostazione predefinita corretta per una configurazione che il container non deve mai riscrivere. Se si dimentica la voce di primo livello volumes:, Compose si interrompe con service "db" refers to undefined volume pgdata.

Avviare lo stack e elencare gli elementi creati da Docker:

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 denominata 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. Impostare esplicitamente il nome con name: nel file Compose oppure definire COMPOSE_PROJECT_NAME se la directory potrebbe essere spostata. Impostazioni di questo tipo vanno inserite insieme agli altri file di ambiente e secret di Compose.

Perché gli errori di autorizzazione riguardano solo i bind mount

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

Quando Docker monta un volume denominato vuoto su una directory che nell'immagine contiene già dei dati, copia quei dati nel volume mantenendo il proprietario e i permessi impostati dall'immagine. L'immagine ufficiale di Postgres fornisce /var/lib/postgresql/data con il proprietario impostato sul proprio utente postgres, quindi il volume risulta appartenere allo stesso id numerico e il database si avvia.

Un bind mount funziona al contrario. Il container vede ciò che si trova sull'host, inclusa la proprietà, mentre il contenuto dell'immagine in quel percorso viene nascosto. Se la directory sull'host non esiste, il daemon Docker la crea e il daemon viene eseguito come root; di conseguenza si ottiene una directory di proprietà di root:root. Un processo del container eseguito come utente non root non può quindi scrivervi:

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

La soluzione consiste nel rendere uguali i valori numerici. La proprietà dei file attraverso un bind mount viene confrontata usando l'user id numerico, non 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 con quel numero oppure forza il container a usare il tuo numero tramite user: "1000:1000" nel servizio. Impostare user: è più semplice da gestire per un'applicazione sviluppata internamente. Modificare il proprietario della directory sull'host è più sicuro per un'immagine che non hai sviluppato tu, perché alcune immagini avviano l'entrypoint come root, riducono quindi 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, anziché di una directory, smette di funzionare quando un editor sostituisce il file invece di scriverlo 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 seguono lo stesso percorso nel 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 sullo stesso filesystem del resto di Docker, in /var/lib/docker/volumes/. Un bind mount risiede invece nel percorso indicato.

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 e raggiunge la macchina virtuale tramite un livello di condivisione dei file. I carichi di lavoro con molte operazioni su file di piccole dimensioni, come un albero delle dipendenze Node.js o la cache di un framework PHP, risultano sensibilmente più lenti. I volumi denominati restano all'interno della macchina virtuale e non sostengono questo costo. Per questo molti file Compose di sviluppo montano la directory del codice sorgente con un bind mount, ma dichiarano un volume denominato su node_modules.

L'altra differenza effettiva riguarda la posizione in cui vengono scritti i dati. Un bind mount su /mnt/backup salva i dati su quel disco. Un volume denominato viene salvato nel filesystem che contiene /var/lib/docker, solitamente il disco root nel caso di un VPS. Un database che cresce all'interno di un volume denominato riempie lo stesso disco su cui risiedono i log di sistema. Verificalo 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 nessun container fa più riferimento.

Ispezione di 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, utile per un controllo rapido. Non usarlo per modificare i file. Scrivervi come root ripropone il problema di ownership descritto sopra e il percorso è un dettaglio del driver local che gli altri driver di volume non condividono.

Il modo sicuro per esaminarne 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, applica le stesse autorizzazioni visibili al container reale e non lascia nulla dopo l'esecuzione grazie a --rm.

Backup di ogni 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 l'operazione è completata. 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 i dati eseguendo l'operazione inversa in un volume nuovo:

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 preserva la proprietà numerica quando viene eseguito come root all'interno del container. Questo mantiene il volume ripristinato utilizzabile dall'applicazione.

Un'avvertenza vale per entrambi i tipi. Copiare i file di un database mentre il database è in esecuzione produce un archivio di dati che possono cambiare durante la copia e il ripristino può risultare corrotto. 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 includere in una normale procedura di backup cifrato con restic 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. L'utente del container che poteva leggere la directory precedente può quindi continuare a leggere il nuovo volume. Modifica quindi il servizio per usare 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 importante. docker compose down lascia invariati i volumi denominati, mentre docker compose down -v elimina tutti i volumi denominati dichiarati dal progetto e non è possibile annullare l'operazione. Un bind mount sopravvive a entrambi i comandi, perché Docker non è proprietario di 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

Chiediti chi scrive il file. La configurazione che modifichi in un editor di testo e sottoponi a commit in git appartiene a un bind mount, montato :ro, perché vuoi che sia visibile e sotto controllo versione. Lo stato dell'applicazione che non apri mai manualmente appartiene a un named volume, perché Docker imposta correttamente i permessi e i dati non dipendono da un percorso dell'host.

Il caso misto riguarda i contenuti multimediali. Una libreria fotografica viene scritta dall'applicazione, ma viene anche gestita da te e spesso è abbastanza grande da richiedere un disco specifico. Collegala tramite bind mount a un percorso su quel disco e imposta deliberatamente la proprietà una sola volta. Questo è il modello adottato dalla maggior parte degli stack self-hosted: named volume per database e cache, bind mount per la configurazione e per la directory di grandi dimensioni che vuoi gestire direttamente. Un help desk come Chatwoot in esecuzione su un VPS rientra esattamente in questo modello, con Postgres in un named volume e gli allegati caricati in un percorso a cui puoi indirizzare un backup.

FAQ

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

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 named volume è uno spazio di archiviazione che Docker crea e gestisce, identificato tramite nome e dichiarato nel blocco di primo livello volumes:. La distinzione pratica riguarda la gestione: usa i bind mount per le configurazioni che mantieni direttamente e i named volume per i dati gestiti dall'applicazione.

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

Un named volume vuoto viene inizializzato copiando i dati dall'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 in cui si trova; se Docker ha dovuto crearla, l'ha resa di proprietà di 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 salva Docker i named volume sul disco?

Con il driver predefinito local, i named volume 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 soltanto tramite un container, perché la modifica come root sull'host cambia la proprietà dei file in modi che il container non prevede.

Come posso eseguire il backup di un named volume?

Avvia un container temporaneo con il volume e una directory dell'host montati entrambi, quindi crea un archivio dall'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, usa lo strumento del database per eseguire il dump invece di copiare i file mentre sono in uso, perché una copia eseguita durante le scritture può essere ripristinata in uno stato corrotto.

docker compose down elimina i miei volumi?

docker compose down rimuove i container e le reti, lasciando in posizione i named volume. docker compose down -v elimina inoltre tutti i named volume 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.