5.7 KiB
SMB Share Backup via docker-compose
Overview
Add a dedicated samba-backup container based on Alpine that:
- Mounts the local
/home/padriano/sharedirectory directly (same bind-mount as thesambacontainer, read-only) - Mounts the remote SMB share via
cifs-utilsinside the container at sync time - Runs
rsyncon a cron schedule to sync from local to remote
This is self-contained in docker-compose, requires no new tools on the host, and survives restarts.
Container choice
A minimal alpine image with rsync + cifs-utils + crond. Alpine is preferred for simplicity and control.
Architecture
flowchart LR
subgraph host [Host filesystem]
share["/home/padriano/share"]
end
subgraph compose [docker-compose]
samba["samba\n192.168.1.247\n[Share]"]
backup["samba-backup\n(alpine + rsync + cifs-utils)"]
end
subgraph remote [Remote SMB server]
remoteShare["\\\\remote-server\\ShareName"]
end
share -->|"bind mount (ro)"| samba
share -->|"bind mount (ro)"| backup
backup -->|"cifs mount + rsync (scheduled)"| remoteShare
Files to change / add
docker-compose.yml— addsamba-backupservicesamba-backup/entrypoint.sh— script: mounts remote share on each cron run, runs rsyncsamba-backup/.credentials— remote SMB credentials (added to.gitignore).env/.env.example— addBACKUP_REMOTE_HOST,BACKUP_REMOTE_SHARE,BACKUP_CRON_SCHEDULE
New service in docker-compose.yml
samba-backup:
image: alpine:latest
container_name: samba-backup
restart: unless-stopped
cap_add:
- SYS_ADMIN # required for mount inside container
security_opt:
- apparmor:unconfined # needed for CIFS mount
devices:
- /dev/fuse
environment:
- TZ=${TZ}
- BACKUP_REMOTE_HOST=${BACKUP_REMOTE_HOST}
- BACKUP_REMOTE_SHARE=${BACKUP_REMOTE_SHARE}
- BACKUP_CRON_SCHEDULE=${BACKUP_CRON_SCHEDULE:-0 3 * * *}
volumes:
- /home/padriano/share:/source:ro
- ./samba-backup/entrypoint.sh:/entrypoint.sh:ro
- ./samba-backup/.credentials:/etc/samba-backup/credentials:ro
entrypoint: ["/bin/sh", "/entrypoint.sh"]
SYS_ADMIN+apparmor:unconfinedare required so the Alpine container can runmount.cifsinternally.
Handling remote unavailability
The remote SMB share can be unreachable in two situations:
1. At container startup — the entrypoint does NOT mount at startup. crond is started unconditionally, so the container never crashes due to a missing remote.
2. At sync time (cron fires, remote is down) — the do_backup function tries to mount, skips rsync gracefully if mount fails, then unmounts. The next cron run will try again. No crash, no data loss.
flowchart TD
start([Container starts]) --> install[apk install deps]
install --> cron[Register cron job]
cron --> crond[crond -f loop]
crond -->|"schedule fires"| tryMount{"mount remote\nSMB share"}
tryMount -->|"success"| rsync[rsync /source/ → /mnt/remote-smb/]
tryMount -->|"failure\n(log + skip)"| skip[Skip — log warning]
rsync --> umount[umount remote share]
umount --> crond
skip --> crond
entrypoint.sh
#!/bin/sh
# No set -e: we handle errors explicitly so the container stays up
apk add --no-cache rsync cifs-utils tzdata > /dev/null 2>&1
MOUNT_TARGET=/mnt/remote-smb
LOG=/var/log/samba-backup.log
mkdir -p "$MOUNT_TARGET"
do_backup() {
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Starting backup run" >> "$LOG"
# Attempt mount; skip sync if unavailable
if ! mount -t cifs "//${BACKUP_REMOTE_HOST}/${BACKUP_REMOTE_SHARE}" "$MOUNT_TARGET" \
-o credentials=/etc/samba-backup/credentials,vers=3.0,uid=0,gid=0,file_mode=0660,dir_mode=0770 \
> /dev/null 2>&1; then
echo "[$TIMESTAMP] WARNING: Remote share unavailable, skipping sync" >> "$LOG"
return 0
fi
rsync -avz --delete /source/ "${MOUNT_TARGET}/" >> "$LOG" 2>&1
RSYNC_EXIT=$?
umount "$MOUNT_TARGET" 2>/dev/null || true
if [ "$RSYNC_EXIT" -ne 0 ]; then
echo "[$TIMESTAMP] ERROR: rsync exited with code ${RSYNC_EXIT}" >> "$LOG"
else
echo "[$TIMESTAMP] Backup completed successfully" >> "$LOG"
fi
}
# Write cron job that calls this script in sync-only mode
cat > /etc/crontabs/root << EOF
${BACKUP_CRON_SCHEDULE} /bin/sh /entrypoint.sh --sync-only
EOF
# If called with --sync-only, just run the backup and exit (used by crond)
if [ "$1" = "--sync-only" ]; then
do_backup
exit 0
fi
# Normal startup: run an immediate first sync in background, then hand off to crond
do_backup &
crond -f -l 8
samba-backup/.credentials
username=REMOTE_USER
password=REMOTE_PASSWORD
domain=WORKGROUP
.env additions
BACKUP_REMOTE_HOST=192.168.x.x # remote SMB server IP or hostname
BACKUP_REMOTE_SHARE=ShareName # share name on remote server
BACKUP_CRON_SCHEDULE=0 3 * * * # daily at 03:00; adjust as needed
Notes
--deleteon rsync makes the remote a true mirror; remove it if you want additive-only backups.- If you prefer not to give
SYS_ADMINto a container, mount the remote CIFS share on the host and pass it as a second bind-mount volume — removes the privileged requirement but requires host-side setup (/etc/fstaborsystemd.mount). - Consider a custom image (
FROM alpine+RUN apk add rsync cifs-utils tzdata) to avoidapkrunning on every container restart. - Backup logs are written to
/var/log/samba-backup.loginside the container. Tail them with:docker exec samba-backup tail -f /var/log/samba-backup.log