Taming ZimaOS – Building a reboot-proof scheduled backup system for my NAS

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:

  1. AppData: Stored on the internal SSD (for maximum performance).
  2. My own data: On 2 spinning disks (RAID1). And a weekly rsync of AppData to this RAID1 set.
  3. 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

  1. Persistence: Since the config resides in a Portainer Stack, the scheduler automatically restarts after a reboot.
  2. Timezone sync: By setting TZ and mounting /etc/localtime (optional), your log timestamps match your local time.
  3. 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.

1 Like

Great write-up
This is a clean, ZimaOS-native way to solve scheduled backups.

You’ve correctly identified the key constraint: anything outside /DATA is ephemeral, while Docker state survives reboots. Using Portainer + Ofelia to anchor scheduling inside Docker is the right move, and it neatly avoids cron/Zima-Cron getting wiped.

Stopping containers for data consistency, using rsync for exact mirrors, and keeping everything reboot-proof without SSH is spot on. The USB disk behavior after reboot is a valid pain point too, worth a feature request.

Nice solution, and very useful for anyone running a serious NAS on ZimaOS.

1 Like