Attached is a Bash script to live back up a Raspberry Pi running Docker to a QNAP NAS.
NAS configuration:
First, create a shared folder on your NAS and a user account with appropriate permissions.
Share name: NetBackup
NAS IP used in the example: 10.1.1.10.
Username in example: YOURNASSHAREUSER
Password in example: YOURSHAREUSERPASSWORD
So your NAS configuration is done.
Raspberry Pi Configuration:
# packages
sudo apt update
sudo apt install -y cifs-utils pigz
# mountpoint and access (edit your credentials)
sudo mkdir -p /mnt/qnap
echo -e "username=YOURNASSHAREUSER\npassword=YOURSHAREUSERPASSWORD\ndomain=WORKGROUP" | sudo tee /etc/cifs-creds-qnap >/dev/null
sudo chmod 600 /etc/cifs-creds-qnapCode-Sprache: JavaScript (javascript)
# persistent mount (edit your NAS IP)
echo "//10.1.1.10/NetBackup /mnt/qnap cifs _netdev,credentials=/etc/cifs-creds-qnap,iocharset=utf8,vers=3.0,serverino,nofail 0 0" | sudo tee -a /etc/fstabCode-Sprache: PHP (php)
# mount & check
sudo mount -a
ls -la /mnt/qnap
# folder create
sudo mkdir -p /mnt/qnap/rpi4/fullimages
# scriptfile create
sudo vi /usr/local/sbin/rpi_fullimage.sh
# copy script in scriptfile (edit Retention and device defaults)
sudo tee /usr/local/sbin/rpi_fullimage.sh >/dev/null <<'SH'
#!/usr/bin/env bash
set -euo pipefail
# ========= Einstellungen =========
TARGET_DIR="/mnt/qnap/rpi4/fullimages" # Zielordner auf QNAP (NetBackup -> /mnt/qnap)
RETENTION_DAYS=180 # Aufbewahrungsdauer in Tagen
BLOCK_SIZE="16M" # Lese-Blockgröße für dd
DEVICE_DEFAULT="/dev/mmcblk0" # or /dev/sda if SDD over USB: df -h
# ========= Hilfsfunktionen =========
abort() { echo "[FEHLER] $*" >&2; exit 1; }
info() { echo "[*] $*"; }
ok() { echo "[OK] $*"; }
# ========= Vorbedingungen =========
command -v pigz >/dev/null || abort "pigz fehlt. Installiere: sudo apt install -y pigz"
mountpoint -q /mnt/qnap || abort "QNAP nicht gemountet (/mnt/qnap). Prüfe /etc/fstab und 'sudo mount -a'."
mkdir -p "$TARGET_DIR"
DEV="$DEVICE_DEFAULT"
[ -b "$DEV" ] || abort "Blockgerät $DEV nicht gefunden. Bootest du evtl. von USB/NVMe? DEVICE_DEFAULT anpassen."
# Grobe Platzprüfung (mind. ~50% der Gerätegröße frei; gzip komprimiert stark)
FREE_KB=$(df -Pk "$TARGET_DIR" | awk 'NR==2{print $4}')
SECTORS=$(cat /sys/block/$(basename "$DEV")/size)
TOTAL_BYTES=$(( SECTORS * 512 ))
NEEDED_KB=$(( (TOTAL_BYTES / 2) / 1024 ))
[ "$FREE_KB" -ge "$NEEDED_KB" ] || abort "Zu wenig Platz auf QNAP. Frei: ${FREE_KB}KB, benötigt grob >= ${NEEDED_KB}KB."
DATE="$(date +%F_%H-%M-%S)"
IMG="${TARGET_DIR}/rpi4-${DATE}.img.gz"
# ========= Docker: laufende Container pausieren =========
DOCKER_AVAILABLE=true
command -v docker >/dev/null || DOCKER_AVAILABLE=false
RUNNING_IDS=""
if $DOCKER_AVAILABLE; then
RUNNING_IDS="$(docker ps -q || true)"
docker_restore() {
if [ -z "$RUNNING_IDS" ]; then return 0; fi
for id in $RUNNING_IDS; do
st="$(docker inspect -f '{{.State.Status}}' "$id" 2>/dev/null || echo unknown)"
[ "$st" = "paused" ] && docker unpause "$id" >/dev/null 2>&1 || true
st="$(docker inspect -f '{{.State.Status}}' "$id" 2>/dev/null || echo unknown)"
if [ "$st" = "exited" ] || [ "$st" = "created" ]; then docker start "$id" >/dev/null 2>&1 || true; fi
done
}
trap 'docker_restore' EXIT
[ -n "$RUNNING_IDS" ] && { info "Pausiere laufende Docker-Container…"; docker pause $RUNNING_IDS || true; }
fi
sync
# ========= Image erstellen (fsync am Zieldatei-dd) =========
info "Erzeuge Image von ${DEV} -> ${IMG}"
dd if="$DEV" bs="$BLOCK_SIZE" status=progress iflag=fullblock \
| pigz -c \
| dd of="$IMG" bs=4M status=progress conv=fsync
# ========= Prüfsumme =========
info "Erzeuge SHA256-Prüfsumme…"
sha256sum "$IMG" > "${IMG}.sha256"
# ========= Docker-Vorzustand wiederherstellen =========
if $DOCKER_AVAILABLE; then
docker_restore
trap - EXIT
fi
# ========= Aufräumen =========
info "Entferne Backups älter als ${RETENTION_DAYS} Tage…"
find "$TARGET_DIR" -type f -name "rpi4-*.img.gz" -mtime +$RETENTION_DAYS -delete
find "$TARGET_DIR" -type f -name "rpi4-*.img.gz.sha256" -mtime +$RETENTION_DAYS -delete
ok "Voll-Image abgeschlossen: $IMG"
SHCode-Sprache: PHP (php)
# make script executable
sudo chmod +x /usr/local/sbin/rpi_fullimage.sh
# run script (docker will suspend)
sudo /usr/local/sbin/rpi_fullimage.sh
After reboot if auto mount not work:
# mount & check
sudo mount -a
ls -la /mnt/qnap
# run script (docker will suspend)
sudo /usr/local/sbin/rpi_fullimage.sh
Backup restore (test first on an other drive):
# Linux
sha256sum -c rpi4-*.img.gz.sha256
lsblk
gunzip -c rpi4-*.img.gz | sudo dd of=/dev/sdX bs=16M status=progress conv=fsync
syncCode-Sprache: JavaScript (javascript)
# Mac
diskutil list
diskutil unmountDisk /dev/disk3
gunzip -c rpi4-*.img.gz | sudo dd of=/dev/rdisk3 bs=16m status=progress
diskutil eject /dev/disk3Code-Sprache: JavaScript (javascript)
# Windows / Mac / Linux
Raspberry Pi Imager oder balenaEtcher 
