Backup e ripristino dei volumi Docker su un VPS
Tre strategie di backup per i volumi Docker su un VPS: snapshot tar, dump nativi di database e backup automatizzati cifrati con offen/docker-volume-backup. Include pianificazione con cron, copie off-site su S3 con rclone e un test di ripristino completo su un server nuovo.
I volumi Docker con nome contengono i tuoi dati di produzione: database, upload, stato di configurazione. I container sono usa e getta. I volumi no. Se un disco si guasta o una migrazione va storta, sono i volumi che devi recuperare.
Questa guida copre tre strategie di backup, le automatizza con cron, invia copie fuori dal server con rclone, e poi dimostra che il ripristino funziona ricostruendo tutto su un VPS nuovo. Se non hai testato il tuo ripristino, non hai backup.
Cosa imparerai:
- Snapshot dei volumi con tar (semplice, universale)
- Dump nativi di database con
pg_dumpemysqldump(consistenti, senza downtime) - Backup automatizzati e cifrati con offen/docker-volume-backup (pianificati, pronti per S3)
- Automazione con cron e policy di retention
- Copie off-site verso storage compatibile S3 tramite rclone
- Ripristino completo in caso di disastro su un VPS nuovo
Prerequisiti
Ti serve un VPS con Debian 12 o Ubuntu 24.04 con Docker e Docker Compose v2 installati. Questa guida presuppone che tu abbia uno stack Compose in esecuzione con almeno un volume con nome. Se devi prima configurarlo, consulta la nostra guida su Docker Compose multi-servizio su un VPS.
Verifica la tua installazione:
docker --version
# Docker version 28.x or newer
docker compose version
# Docker Compose version v2.x or newer
Controlla i tuoi volumi esistenti:
docker volume ls
L'output elenca ogni volume con nome nel sistema. Identifica quali contengono dati importanti. Usa docker system df -v per vedere quanto spazio occupa ogni volume. Questo aiuta a stimare le dimensioni dei backup e le esigenze di storage.
Crea una directory di backup con permessi ristretti:
mkdir -p /opt/backups/docker
chmod 700 /opt/backups/docker
Solo root può leggere questa directory. I backup contengono spesso credenziali di database, token di sessione o dati utente.
Come faccio il backup dei volumi Docker su un VPS?
Ci sono tre strategie, ognuna con compromessi diversi. Scegli in base al contenuto dei tuoi volumi e a quanto downtime puoi tollerare.
| Metodo | Downtime | Consistenza dati | Automazione | Cifratura | Ideale per |
|---|---|---|---|---|---|
| Snapshot tar | Breve (container fermato) | Livello filesystem | Manuale o script cron | No (aggiungere GPG separatamente) | File statici, upload, config |
| Dump database | Nessuno | Consistenza transazionale | Manuale o script cron | No (aggiungere GPG separatamente) | PostgreSQL, MySQL, MariaDB |
| offen/docker-volume-backup | Opzionale (configurabile) | Livello filesystem | Scheduler integrato | GPG integrato | Qualsiasi volume, operazione autonoma |
Come creo un backup tar di un volume Docker?
Ferma il container che usa il volume, esegui un container Alpine temporaneo per archiviare il contenuto del volume con tar, poi riavvia. L'operazione richiede pochi secondi per la maggior parte dei volumi e funziona con qualsiasi tipo di dati.
1. Ferma il container che scrive nel volume:
# Replace "app" with your service name from docker-compose.yml
docker compose stop app
Fermare lo scrittore previene scritture parziali durante l'archiviazione. Per volumi in sola lettura o file statici, puoi saltare questo passaggio.
2. Crea l'archivio tar:
docker run --rm \
-v myapp_data:/source:ro \
-v /opt/backups/docker:/backup \
alpine tar czf /backup/myapp_data-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .
Cosa fa: avvia un container Alpine usa e getta, monta il tuo volume in sola lettura su /source, monta la directory di backup su /backup, e crea un archivio tar compresso con gzip. Il flag --rm elimina il container quando finisce. Il flag :ro impedisce al processo di backup di scrivere accidentalmente nei tuoi dati.
3. Riavvia il container:
docker compose start app
4. Verifica l'archivio:
ls -lh /opt/backups/docker/myapp_data-*.tar.gz
L'output mostra l'archivio con una dimensione ragionevole. Un volume da 500 MB si comprime tipicamente a 60-120 MB a seconda del tipo di dati.
Elenca il contenuto dell'archivio per confermare che i file ci sono:
tar tzf /opt/backups/docker/myapp_data-20260319-120000.tar.gz | head -20
Occhio: i percorsi devono iniziare con ./ (senza nome di directory iniziale). Questo perché abbiamo usato -C /source . nel comando tar. Questo è importante durante il ripristino.
Come faccio il backup di un database PostgreSQL in Docker?
Usa pg_dump all'interno del container in esecuzione. Questo produce un dump transazionalmente consistente senza fermare il database. Il formato custom (-Fc) comprime l'output e supporta il ripristino selettivo.
docker compose exec -T postgres pg_dump \
-U "$POSTGRES_USER" \
-Fc \
--no-owner \
--no-acl \
mydb > /opt/backups/docker/mydb-$(date +%Y%m%d-%H%M%S).dump
Cosa fa: exec -T esegue il comando nel container postgres in esecuzione senza allocare un TTY (necessario per il piping dell'output). -Fc seleziona il formato custom, che è compresso e supporta pg_restore. --no-owner e --no-acl rendono il dump portabile tra diversi utenti database.
La variabile $POSTGRES_USER deve provenire dal tuo file di ambiente, non essere scritta direttamente nel codice. Se il tuo stack Compose usa un file env:
source /opt/myapp/.env
docker compose exec -T postgres pg_dump \
-U "$POSTGRES_USER" \
-Fc \
--no-owner \
--no-acl \
"$POSTGRES_DB" > /opt/backups/docker/"$POSTGRES_DB"-$(date +%Y%m%d-%H%M%S).dump
Verifica il dump passandolo attraverso il pg_restore del container:
docker compose exec -T postgres pg_restore --list < /opt/backups/docker/mydb-20260319-120000.dump | head -10
Questo stampa l'indice senza ripristinare nulla. Se il file è corrotto, pg_restore restituirà un errore. Usiamo docker compose exec -T perché pg_restore risiede nel container, non sull'host (a meno che non installi postgresql-client separatamente).
Come faccio il backup di un database MySQL in Docker?
Usa mysqldump con --single-transaction per le tabelle InnoDB. Questo fornisce uno snapshot consistente senza bloccare il database.
docker compose exec -T mysql mysqldump \
-u root \
-p"$MYSQL_ROOT_PASSWORD" \
--single-transaction \
--routines \
--triggers \
mydb > /opt/backups/docker/mydb-$(date +%Y%m%d-%H%M%S).sql
Il flag -p non ha spazio prima della password. --single-transaction usa una lettura consistente per le tabelle InnoDB. --routines e --triggers includono stored procedure e trigger che mysqldump salta di default.
Verifica che il dump non sia vuoto e termini con il marker di completamento:
tail -5 /opt/backups/docker/mydb-20260319-120000.sql
L'output mostra -- Dump completed on YYYY-MM-DD HH:MM:SS. Se il file è troncato o vuoto, il dump è fallito.
Come automatizzo i backup dei volumi Docker con offen/docker-volume-backup?
offen/docker-volume-backup funziona come container sidecar nel tuo stack Compose. Esegue il backup dei volumi montati secondo una pianificazione, cifra opzionalmente gli archivi con GPG, e può caricare direttamente su storage compatibile S3. Può anche fermare i container durante il backup per garantire la consistenza.
Aggiungi il servizio di backup al tuo docker-compose.yml:
services:
# ... your existing services ...
backup:
image: offen/docker-volume-backup:v2.47.2
restart: unless-stopped
env_file:
- ./backup.env
volumes:
- myapp_data:/backup/myapp_data:ro
- myapp_db:/backup/myapp_db:ro
- /opt/backups/docker:/archive
- /var/run/docker.sock:/var/run/docker.sock
labels:
- docker-volume-backup.stop-during-backup=false
Cosa fa: monta ogni volume da backuppare sotto /backup/ in sola lettura, monta /archive per lo storage locale dei backup, e monta il socket Docker per permettere allo strumento di fermare e riavviare container quando configurato per farlo. Il tag dell'immagine v2.47.2 fissa la versione. Non usare latest in produzione.
Nota sulla sicurezza: montare il socket Docker dà al container di backup il controllo completo su Docker nell'host. Questo è necessario per la funzione di stop durante il backup. Se non hai bisogno di questa funzione, puoi montarlo in sola lettura (/var/run/docker.sock:/var/run/docker.sock:ro), che permette allo strumento di leggere le label dei container ma gli impedisce di fermarli o avviarli.
Crea il file di ambiente con permessi ristretti:
touch /opt/myapp/backup.env
chmod 600 /opt/myapp/backup.env
# /opt/myapp/backup.env
BACKUP_CRON_EXPRESSION=0 3 * * *
BACKUP_RETENTION_DAYS=7
BACKUP_COMPRESSION=gz
BACKUP_FILENAME=backup-%Y%m%dT%H%M%S.tar.gz
# GPG encryption (generate a strong passphrase)
GPG_PASSPHRASE=your-generated-passphrase-here
# S3-compatible storage (optional, see rclone section for alternative)
# AWS_S3_BUCKET_NAME=my-backups
# AWS_S3_PATH=myapp
# AWS_ENDPOINT=s3.eu-central-1.amazonaws.com
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
Genera la passphrase GPG:
openssl rand -base64 32
Conserva questa passphrase in un posto sicuro fuori dal server. Se la perdi, i backup cifrati saranno irrecuperabili.
Se vuoi che lo strumento di backup fermi specifici container durante il backup per garantire la consistenza del filesystem, aggiungi una label a quei servizi:
services:
app:
# ... your config ...
labels:
- docker-volume-backup.stop-during-backup=true
Avvia il servizio di backup:
docker compose up -d backup
Verifica che sia in esecuzione:
docker compose logs backup
L'output mostra una riga di log che conferma la pianificazione cron. Attendi la prima esecuzione pianificata, oppure avvia un backup manuale:
docker compose exec backup backup
Controlla che l'archivio sia comparso:
ls -lh /opt/backups/docker/
Come pianifico i backup Docker con cron?
Per le strategie tar e dump di database, uno script shell con cron gestisce la pianificazione e la retention. Lo strumento offen ha il suo scheduler; salta questa sezione se usi solo quello.
Crea lo script di backup:
touch /opt/backups/docker-backup.sh
chmod 700 /opt/backups/docker-backup.sh
#!/usr/bin/env bash
# /opt/backups/docker-backup.sh
# Backs up Docker volumes and databases, removes old archives.
set -euo pipefail
BACKUP_DIR="/opt/backups/docker"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
COMPOSE_DIR="/opt/myapp"
# Load database credentials from env file
source "${COMPOSE_DIR}/.env"
cd "$COMPOSE_DIR"
# --- Tar backup of application data volume ---
docker compose stop app
docker run --rm \
-v myapp_data:/source:ro \
-v "${BACKUP_DIR}":/backup \
alpine tar czf "/backup/myapp_data-${TIMESTAMP}.tar.gz" -C /source .
docker compose start app
# --- PostgreSQL dump (no downtime) ---
docker compose exec -T postgres pg_dump \
-U "$POSTGRES_USER" \
-Fc --no-owner --no-acl \
"$POSTGRES_DB" > "${BACKUP_DIR}/${POSTGRES_DB}-${TIMESTAMP}.dump"
# --- Retention: delete backups older than N days ---
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -type f -name "*.dump" -mtime +${RETENTION_DAYS} -delete
echo "[$(date -Iseconds)] Backup completed successfully"
Verifica che lo script funzioni senza errori:
/opt/backups/docker-backup.sh
Controlla i file prodotti:
ls -lh /opt/backups/docker/
Aggiungi una voce cron che si esegua ogni giorno alle 03:00 e registri l'output:
crontab -e
0 3 * * * /opt/backups/docker-backup.sh >> /var/log/docker-backup.log 2>&1
Il 2>&1 redirige stderr nello stesso file di log, così gli errori vengono catturati. Controlla il log dopo la prima esecuzione:
cat /var/log/docker-backup.log
Se lo script fallisce, cron inghiotte silenziosamente l'errore a meno che tu non rediriga l'output. Per ricevere avvisi via email in caso di fallimento, aggiungi questo wrapper:
0 3 * * * /opt/backups/docker-backup.sh >> /var/log/docker-backup.log 2>&1 || echo "Docker backup failed on $(hostname)" | mail -s "BACKUP FAILED" you@example.com
Questo richiede mailutils o un pacchetto simile. Modifica l'indirizzo del destinatario.
Come copio i backup Docker su storage compatibile S3 con rclone?
I backup locali proteggono dai guasti applicativi. Non proteggono dai guasti disco o da un server compromesso. Servono copie off-site. rclone funziona con qualsiasi storage compatibile S3: AWS S3, Backblaze B2, Wasabi, MinIO, OVH Object Storage, Scaleway e altri.
Installa rclone:
apt update && apt install -y rclone
Configura un remote compatibile S3:
rclone config
Segui le istruzioni interattive:
nper un nuovo remote- Chiamalo
s3backup - Scegli
s3(Amazon S3 Compliant Storage Providers) - Seleziona il tuo provider (o "Any other S3 compatible provider")
- Inserisci la tua access key e secret key
- Imposta la regione e l'URL endpoint del tuo provider
- Lascia le altre opzioni ai valori predefiniti
Verifica che il remote funzioni:
rclone lsd s3backup:
Questo elenca i tuoi bucket. Se fallisce, le credenziali o l'endpoint sono sbagliati.
Crea un bucket per i backup (se il tuo provider lo supporta tramite rclone):
rclone mkdir s3backup:my-docker-backups
Sincronizza la tua directory di backup locale con il bucket:
rclone sync /opt/backups/docker s3backup:my-docker-backups/$(hostname)/ \
--transfers 4 \
--checkers 8 \
--log-file /var/log/rclone-backup.log \
--log-level INFO
Cosa fa: sync fa corrispondere il remote alla directory locale. I file cancellati localmente (dalla retention) vengono cancellati anche nel remote. Il prefisso $(hostname) separa i backup se hai più server.
Verifica l'upload:
rclone ls s3backup:my-docker-backups/$(hostname)/
L'output mostra i tuoi file di backup elencati con dimensioni corrispondenti alle copie locali.
Aggiungi rclone sync allo script di backup o come voce cron separata che si esegue dopo il backup:
30 3 * * * rclone sync /opt/backups/docker s3backup:my-docker-backups/$(hostname)/ --transfers 4 --log-file /var/log/rclone-backup.log --log-level INFO
Questo si esegue alle 03:30, dando al job di backup delle 03:00 il tempo di finire.
Proteggi la configurazione rclone: contiene le tue credenziali S3.
chmod 600 ~/.config/rclone/rclone.conf
ls -la ~/.config/rclone/rclone.conf
L'output mostra i permessi -rw-------. Solo root può leggere questo file.
Devo fermare i container prima di fare il backup dei volumi Docker?
Dipende dal contenuto del volume. Sbagliare qui è la causa più comune di backup corrotti.
Database (PostgreSQL, MySQL, MongoDB): non fare mai il tar di un volume database in esecuzione. I file su disco rappresentano uno stato di transazione in corso. Un tar di questi file è come fotocopiare un libro mentre qualcuno ne riscrive i capitoli. Il risultato è internamente inconsistente. Usa invece pg_dump, mysqldump o mongodump. Questi strumenti producono uno snapshot transazionalmente consistente mentre il database continua a funzionare.
Dati applicativi (upload, file statici, config): tar è sicuro se l'applicazione tollera un breve stop. Se l'app scrive continuamente e non puoi fermarla, il tar potrebbe contenere file scritti parzialmente. Per la maggior parte delle applicazioni web, uno stop di 2 secondi durante un backup alle 3 di notte è accettabile.
Redis, store chiave-valore: Redis scrive snapshot RDB su disco periodicamente. Attiva un BGSAVE prima di fare il tar del volume, poi aspetta che finisca. Questo ti dà uno snapshot consistente senza fermare Redis.
docker compose exec redis redis-cli BGSAVE
# Wait a few seconds
docker compose exec redis redis-cli LASTSAVE
La scelta sicura di default: nel dubbio, ferma il container, fai il backup, riavvia. Un breve downtime è meglio di backup corrotti.
Come ripristino i volumi Docker su un nuovo VPS?
Questa è la procedura che dimostra che i tuoi backup funzionano. Installa Docker su un server nuovo, trasferisci i file di backup, ricrea i volumi, ripristina i dati, e verifica che l'applicazione funzioni.
1. Installa Docker sul nuovo VPS
apt update && apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Controllo:
docker --version && docker compose version
2. Trasferisci i file di backup sul nuovo server
Dal tuo computer locale o dal vecchio server:
rsync -avz --progress /opt/backups/docker/ root@NEW_SERVER_IP:/opt/backups/docker/
Oppure scarica da S3:
# On the new server, install and configure rclone first
apt install -y rclone
# Re-run rclone config with the same credentials
rclone copy s3backup:my-docker-backups/OLD_HOSTNAME/ /opt/backups/docker/
Verifica che i file siano arrivati:
ls -lh /opt/backups/docker/
3. Copia i file Compose e i file env
rsync -avz /opt/myapp/ root@NEW_SERVER_IP:/opt/myapp/
Oppure ripristinali dal tuo repository Git. Il tuo docker-compose.yml e .env dovrebbero essere versionati. Il file .env dovrebbe essere nel .gitignore e backuppato separatamente.
4. Ripristina il volume basato su tar
# Create the volume (Docker Compose will also do this on first `up`,
# but creating it explicitly lets us restore data before starting services)
docker volume create myapp_data
# Restore from archive
docker run --rm \
-v myapp_data:/target \
-v /opt/backups/docker:/backup:ro \
alpine sh -c "cd /target && tar xzf /backup/myapp_data-20260319-030000.tar.gz"
Cosa fa: crea il volume con nome, poi esegue un container temporaneo che estrae l'archivio al suo interno. La directory di backup è montata in sola lettura per prevenire incidenti.
Verifica i dati ripristinati:
docker run --rm -v myapp_data:/data:ro alpine ls -la /data/
L'output mostra gli stessi file presenti nel volume originale.
5. Ripristina il database PostgreSQL
Avvia solo il container del database:
cd /opt/myapp
docker compose up -d postgres
Aspetta che sia pronto:
docker compose logs -f postgres
# Wait until you see "database system is ready to accept connections"
Ripristina il dump:
docker compose exec -T postgres pg_restore \
-U "$POSTGRES_USER" \
-d "$POSTGRES_DB" \
--clean \
--if-exists \
--no-owner \
--no-acl \
< /opt/backups/docker/mydb-20260319-030000.dump
Cosa fa: --clean elimina gli oggetti esistenti prima di ricrearli. --if-exists previene errori se gli oggetti non esistono ancora. Questo rende il ripristino idempotente.
Verifica i dati:
docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt"
L'output mostra le tue tabelle elencate. Esegui un conteggio rapido su una tabella nota:
docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT count(*) FROM your_table;"
6. Avvia tutti i servizi e verifica
docker compose up -d
Controlla che tutti i container siano in esecuzione:
docker compose ps
Ogni servizio dovrebbe mostrare Up o running. Se un container è in loop di riavvio, controlla i suoi log:
docker compose logs --tail 50 service_name
Testa l'applicazione dall'esterno del server. Dal tuo computer locale:
curl -I http://NEW_SERVER_IP:PORT
Dovresti ottenere una risposta HTTP valida. Se l'app ha un endpoint di health check, chiamalo:
curl http://NEW_SERVER_IP:PORT/health
Per saperne di più sugli health check, consulta la nostra guida su limiti di risorse e health check di Docker Compose.
Come verifico che un backup di volume Docker sia valido?
Un backup mai testato è un rischio. Esegui questi controlli regolarmente, non solo durante il disaster recovery.
Controlla l'integrità dell'archivio:
# For tar.gz files
gzip -t /opt/backups/docker/myapp_data-20260319-030000.tar.gz && echo "OK" || echo "CORRUPT"
Controlla il contenuto dell'archivio:
tar tzf /opt/backups/docker/myapp_data-20260319-030000.tar.gz | wc -l
Confronta il conteggio dei file con un backup noto come buono. Un calo improvviso nel numero di file indica un problema.
Testa il ripristino su un volume usa e getta:
docker volume create test_restore
docker run --rm \
-v test_restore:/target \
-v /opt/backups/docker:/backup:ro \
alpine sh -c "cd /target && tar xzf /backup/myapp_data-20260319-030000.tar.gz"
# Inspect the restored data
docker run --rm -v test_restore:/data:ro alpine ls -la /data/
# Clean up
docker volume rm test_restore
Verifica un dump di database:
docker compose exec -T postgres pg_restore --list < /opt/backups/docker/mydb-20260319-030000.dump | wc -l
Se restituisce un numero di oggetti (tabelle, indici, sequenze), il dump è leggibile. Se dà errore, il file è corrotto.
Genera checksum per lo storage a lungo termine:
sha256sum /opt/backups/docker/*.tar.gz /opt/backups/docker/*.dump > /opt/backups/docker/checksums-$(date +%Y%m%d).sha256
Carica il file di checksum insieme ai backup. Prima del ripristino, verifica:
sha256sum -c /opt/backups/docker/checksums-20260319.sha256
Risoluzione dei problemi
"Permission denied" durante la creazione dell'archivio tar:
Il container temporaneo gira come root di default, quindi questo di solito significa che la directory di backup non esiste o ha permessi sbagliati. Esegui ls -la /opt/backups/ e verifica che la sottodirectory docker esista con permessi 700.
pg_dump/pg_restore si blocca:
Probabilmente hai dimenticato il flag -T in docker compose exec. Senza -T, exec tenta di allocare un TTY, che blocca durante il piping dell'output. Usa docker compose exec -T.
I file di backup sono di 0 byte:
Il container ha scritto in un percorso diverso da quello previsto. Ricontrolla che il nome del volume in docker volume ls corrisponda a quello usato nel flag -v. I volumi con nome sono case-sensitive.
rclone sync va in timeout:
Le sincronizzazioni iniziali grandi possono superare i timeout di default. Aggiungi --timeout 30m e --retries 3 al comando rclone.
offen/docker-volume-backup non si esegue secondo la pianificazione:
Controlla la sintassi di BACKUP_CRON_EXPRESSION. Lo strumento usa la sintassi cron standard a 5 campi. Esegui docker compose logs backup e cerca errori di parsing.
Il database ripristinato ha permessi sbagliati:
Hai usato un dump senza --no-owner. Il dump tenta di impostare la proprietà all'utente originale, che potrebbe non esistere sul nuovo server. Rifai il dump con --no-owner --no-acl oppure esegui REASSIGN OWNED BY old_user TO new_user; in psql.
Per approfondire
- Configurazione completa di Docker in produzione
- Health check che confermano che i servizi sono attivi dopo un ripristino
- Le basi della CLI Docker
Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.