# SMB Share Backup via docker-compose ## Overview Add a dedicated `samba-backup` container based on Alpine that: 1. Mounts the local `/home/padriano/share` directory directly (same bind-mount as the `samba` container, read-only) 2. Mounts the remote SMB share via `cifs-utils` inside the container at sync time 3. Runs `rsync` on 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 ```mermaid 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`](../docker-compose.yml) — add `samba-backup` service - [`samba-backup/entrypoint.sh`](../samba-backup/entrypoint.sh) — script: mounts remote share on each cron run, runs rsync - [`samba-backup/.credentials`](../samba-backup/.credentials) — remote SMB credentials (added to `.gitignore`) - [`.env`](../.env) / [`.env.example`](../.env.example) — add `BACKUP_REMOTE_HOST`, `BACKUP_REMOTE_SHARE`, `BACKUP_CRON_SCHEDULE` ## New service in docker-compose.yml ```yaml 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:unconfined` are required so the Alpine container can run `mount.cifs` internally. ## 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. ```mermaid 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 ```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 ```ini 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 - `--delete` on rsync makes the remote a true mirror; remove it if you want additive-only backups. - If you prefer not to give `SYS_ADMIN` to 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/fstab` or `systemd.mount`). - Consider a custom image (`FROM alpine` + `RUN apk add rsync cifs-utils tzdata`) to avoid `apk` running on every container restart. - Backup logs are written to `/var/log/samba-backup.log` inside the container. Tail them with: ``` docker exec samba-backup tail -f /var/log/samba-backup.log ```