Just wanted to share this with you…
Introduction
ZimaOS is sleek, fast, and “immutable”. While immutability is great for stability, it can be a nightmare for anyone trying to set up a simple, scheduled backup that survives a reboot. After a weekend of fighting disappearing crontabs and failing native apps, I finally found my “golden method.”
If you’re tired of the native Backup app or schedulers that forget their tasks after a restart, this guide might be for you.
I have built a system with the following storage:
- AppData: Stored on the internal SSD (for maximum performance).
- My own data: On 2 spinning disks (RAID1). And a weekly rsync of AppData to this RAID1 set.
- Off-site Backup: Weekly rsync from RAID1 disks to a 24/7 plugged-in USB disk (for disaster recovery/offsite rotation).
Feature request: Can you make a plugged in USB disk not appear as a new disk after every reboot?
Why standard tools fail on ZimaOS
- Native Backup App: Not appropriate for me due to a size mismatch between source and destination with a manual backup (without versioning). I needed more reliability with an exact copy and I needed a scheduler. The mismatch is possibly due to macOS and Windows ‘hidden’files (I use both OS-es) but a lack of a scheduler made me really look further.
- System Crontab: Wiped clean upon every reboot due to the immutable OS structure.
- Zima-Cron App: Non-persistent (all tasks gone after a reboot) and fails to create new tasks after a restart. This “module” is not installed by default in ZimaOS but can be added with 1 command. I am not sure if this is even supported
The solution: Portainer + Ofelia
The only place ZimaOS does not wipe during a reboot is the Docker database located on the /DATA partition. Therefore, I used Portainer to run an Ofelia scheduler as a “Stack”. This ensures the configuration is stored on the internal SSD disk and is persistant.
Step 1: The scripts (stored on my RAID1)
Create a folder, in my case on my RAID1 (e.g., /media/RAID1/scripts) and place your backup scripts there.
Crucial: Since the Ofelia image is “slim,” I added a check to install the docker-cli within the container (it only takes about 2 seconds). This allows the script to stop/start other containers (like Plex) during backup.
Example: backup_appdata.sh
#!/bin/sh
LOGFILE=“/scripts/backup_appdata.log”
The fix for Docker-in-Docker which only takes a few seconds
if ! command -v docker >/dev/null 2>&1; then
apk add --no-cache docker-cli >> “$LOGFILE” 2>&1
fi
echo “— AppData Backup started on $(date) —” >> “$LOGFILE”
Stop Plex to ensure database integrity
echo “Stopping Plex…” >> “$LOGFILE”
docker stop plex >> “$LOGFILE” 2>&1
sleep 5
Run rsync via a one-shot container
echo “Starting rsync…” >> “$LOGFILE”
docker run --rm
–user root
-v /DATA/AppData:/source:ro
-v /media/RAID1/AppData_backup:/destination
instrumentisto/rsync-ssh
rsync -avH --delete /source/ /destination/ >> “$LOGFILE” 2>&1
Restart Plex
echo “Starting Plex…” >> “$LOGFILE”
docker start plex >> “$LOGFILE” 2>&1
echo “— Backup finished on $(date) —” >> “$LOGFILE”
echo “-------------------------------------------” >> “$LOGFILE”
Step 2: The Portainer Stack
Install Portainer from the ZimaOS App Store. Create a new Stack named backup-scheduler and paste this YAML in. This is the “alarm clock” that never gets wiped.
services:
scheduler:
image: mcuadros/ofelia:latest
container_name: backup_scheduler
user: root
privileged: true
network_mode: host
environment:
- TZ=Europe/Amsterdam # Set to your local timezone
volumes: - /var/run/docker.sock:/var/run/docker.sock
- /media/RAID1/scripts:/scripts:rw
command: daemon -d
labels:
ofelia.enabled: “true”
Job 1: AppData (Every Sunday at 23:30)
ofelia.job-local.appdata.schedule: “0 30 23 * * 0”
ofelia.job-local.appdata.command: “/bin/sh /scripts/backup_appdata.sh”
Job 2: NAS home (Every Sunday at 23:45)
ofelia.job-local.nas_home.schedule: “0 45 23 * * 0”
ofelia.job-local.nas_home.command: “/bin/sh /scripts/backup_nas_home.sh”
restart: always
Why this works
- Persistence: Since the config resides in a Portainer Stack, the scheduler automatically restarts after a reboot.
- Timezone sync: By setting TZ and mounting /etc/localtime (optional), your log timestamps match your local time.
- Authority: The container manages other containers via the docker.sock mount.
Conclusion and tips
- Testing: Use the Portainer console and type sh /scripts/backup_appdata.sh for an instant manual run.
- Logging: Use the tail command in the Portainer console. Or open the logfile with Files or on some pc/mac (when the folder is shared on the network).
- Reboot: After a reboot, the system restores itself within 60 seconds. No SSH required!
Happy self-hosting! If you found this useful, let me know in the comments!
P.S. You do get an extra tile in the ZimaOS portal as a “Legacy app” because the container in the Portainer stack is not managed bij ZimaOS, but by Portainer.
P.S.2. Hashtags cannot be used in this forum, instead lines that start with a hashtag are displayed with a larger font. When you use the scripts, be aware.