Unraid runs entirely in RAM from your USB flash drive. If you download a binary straight to /usr/local/bin, it will vanish the moment you reboot your server. [1, 2]
Follow this standard Unraid method to make Restic persist across system reboots:
Step 1: Save the Binary to Your Flash Drive
- Go to the official Restic GitHub Releases Page and download the latest stable Linux AMD64 file (e.g.,
restic_x.x.x_linux_amd64.bz2). - Extract the
.bz2file on your computer to get the rawresticbinary file. - Plug your Unraid flash drive into your computer (or access it over the network via the
bootSMB share). - Create a folder named
extraon the flash drive if it doesn’t exist, and drop theresticfile inside it:
mkdir -p /boot/extra && cd /boot/extra
wget https://github.com/restic/restic/releases/download/v0.19.0/restic_0.19.0_linux_amd64.bz2
bunzip2 restic_0.19.0_linux_amd64.bz2
cp restic_0.19.0_linux_amd64 restic
chmod +x restic
Step 2: Force Unraid to Load it at Boot
To make Unraid load Restic into system memory automatically every time the server starts up, add it to your go script: [1, 2]
- Open
/boot/config/goon your flash drive using a text editor (like Notepad++ or VS Code). - Append the following lines to the very bottom of the file
# Copy and activate Restic binary on boot
cp /boot/extra/restic /usr/local/bin/restic
chmod 755 /usr/local/bin/restic
- Save and close the file.
(To avoid rebooting right now to test this, simply paste those two command lines directly into your current Unraid web terminal to install it instantly).
How to Initialize and Configure Your First Backup Repo
Before Restic can process automated backups, you must initialize an encrypted repository destination where your deduplicated snapshots will be safely stored. [1, 2]
Step 1: Create the Backup Repository
Open your Unraid terminal. We will initialize a local repository targeting your main HDD Array storage pool
mkdir -p /mnt/user/bak_appdata/restic/data
restic init --repo /mnt/user/bak_appdata/restic/data
The terminal will prompt you to type a password. Do not lose this password. Every piece of data sent to this directory is encrypted using AES-256 by default; without the password, your data is completely unrecoverable. [1, 2]
Step 2: Use the Automation Script
Now that the binary is installed globally and the target directory is initialized, you can deploy the automated backup-gitea.sh script provided in the previous step. The script will handle:
- Running a live, non-destructive dump of the MySQL container databases.
- Scanning your Gitea appdata folders for structural changes.
- Injecting only newly created data chunks into the encrypted repository.
- Trimming away historical versions that fall outside your set retention periods.
cat restic-gitea-backup.sh
#!/bin/bash
set -e
# --- CONFIGURATION ---
BACKUP_TAG="gitea-production"
SRC_DATA_DIR="/mnt/nvme-pool/appdata/dc-gitea/gitea"
TEMP_DB_DIR="/mnt/nvme-pool/appdata/dc-gitea/tmp_db"
RESTIC_REPO="/mnt/user/bak_appdata/restic/data" # Store snapshots on your main HDD Array Share
# Define an encryption key for your backup repository (Keep this safe!)
export RESTIC_PASSWORD="myd0gsk1p"
# --- START BACKUP PROCESS ---
echo "Creating temporary database staging directory..."
mkdir -p "$TEMP_DB_DIR"
echo "Executing consistent hot-dump of Gitea MySQL database..."
# Pulls db configuration variables dynamically out of your running stack env
docker compose -f /mnt/nvme-pool/appdata/dc-gitea/docker-compose.yml exec -T db \
mysqldump -u root -p"$(grep DB_PASSWORD /mnt/nvme-pool/appdata/dc-gitea/.env | cut -d'=' -f2)" gitea > "$TEMP_DB_DIR/gitea_db.sql"
# Initialize Restic repository if it doesn't exist yet
if [ ! -d "$RESTIC_REPO" ]; then
echo "Initializing new Restic backup repository..."
restic -r "$RESTIC_REPO" init
fi
echo "Running deduplicated incremental snapshot..."
restic -r "$RESTIC_REPO" backup \
--tag "$BACKUP_TAG" \
"$SRC_DATA_DIR" \
"$TEMP_DB_DIR/gitea_db.sql"
echo "Cleaning up local database dump..."
rm -f "$TEMP_DB_DIR/gitea_db.sql"
rmdir "$TEMP_DB_DIR"
echo "Pruning old historical snapshots based on retention policy..."
# Keeps 7 daily snapshots, 4 weekly snapshots, and 12 monthly snapshots
restic -r "$RESTIC_REPO" forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--prune
echo "Gitea Incremental Backup Successfully Completed!"
Useful Commands to Manage Your Globally Stored Backups
Once your script starts running daily, you can open your Unraid terminal at any time and run these basic management commands:
- List all historical backup instances:
restic -r /mnt/user/backups/gitea-restic snapshots
- Check the real storage footprint and deduplication stats:
restic -r /mnt/user/backups/gitea-restic stats
- Browse or verify file integrity inside a backup
restic -r /mnt/user/backups/gitea-restic check
Risk of High CPU/RAM Starvation
Restic heavily compresses, hashes (SHA-256), and encrypts data chunks on the fly.
- The Risk: If you back up large datasets (like your 85GB Gitea stack), a bare-metal Restic process will consume as much CPU and RAM as Unraid will give it. This can cause temporary performance stuttering in your other active Docker containers or plex streams.
- The Mitigation: Unlike a Docker container where you can easily set a hard RAM limit, on bare metal you must restrict it manually by prepending
niceandioniceto your script commands to force Restic to run at the lowest priority:
nice -n 19 ionice -c 3 restic -r /mnt/user/bak_appdata/restic/data backup ...
Part 1: How to Restore Gitea From Your Backup
If your NVMe drive fails or you lose data, the restore process takes under 5 minutes. You spin up your basic container environment, restore the files from Restic, and drop the database backup back into MySQL. [1]
Step 1: Stop the Gitea App Container
Keep your database and Redis containers running, but shut down the Gitea application container so it does not lock files:
docker compose stop server runner
Step 2: Extract Your Files Using Restic
Run the Restic restore command. You can choose to restore everything, or use the --target path to extract files exactly where they belong:
# Get your snapshot ID first
restic -r /mnt/user/bak_appdata/restic/data snapshots
# Restore the files straight back to your NVMe appdata pool
restic -r /mnt/user/bak_appdata/restic/data restore LATEST_SNAPSHOT_ID --target /
(This places your gitea/ folder data and the gitea_db.sql file back onto your disk).
Step 3: Restore the MySQL Database
Inject your text-based database dump back into your running MySQL container instance:
docker compose exec -T db mysql -u root -p"YOUR_DB_PASSWORD" gitea < /mnt/nvme-pool/appdata/dc-gitea/tmp_db/gitea_db.sql
Step 4: Restart the Stack
docker compose start server runner
Pro-Tip for Nextcloud & Rocket.Chat Backups
Unlike Git repositories, which are completely immutable, users can write files to Nextcloud while Restic is mid-scan, causing potential file index warning errors during a snapshot.
To achieve 100% data integrity for your other apps, modify your backup script to put Nextcloud into Maintenance Mode before running the backup, and turn it off immediately after Restic finishes:
# Enable Maintenance Mode (Freezes user web writes instantly)
docker exec -u www-data nextcloud-app-container php occ maintenance:mode --on
# ... [ Run your DB Dump & Restic Backup Here ] ...
# Disable Maintenance Mode
docker exec -u www-data nextcloud-app-container php occ maintenance:mode --off
Part 2: Does This Process Work for Nextcloud and Rocket.Chat?
Yes, the core architectural concept is identical, but the command syntax changes slightly depending on the database engine. You always extract the live database to a text file first, then let Restic snapshot the files and the database dump together.
For Nextcloud (MySQL / MariaDB)
Nextcloud operates exactly like Gitea. You use the exact same mysqldump command structure.
The Backup Script Modification:
# Dump the Nextcloud database
docker compose -f /path/to/dc-nextcloud/docker-compose.yml exec -T db \
mysqldump -u root -p"NEXTCLOUD_DB_PASS" nextcloud > /tmp_db/nextcloud_db.sql
# Run Restic snapshot
restic -r /mnt/user/backups/nextcloud-restic backup /mnt/user/appdata/nextcloud /tmp_db/nextcloud_db.sql
The Restore Command:
docker compose exec -T db mysql -u root -p"NEXTCLOUD_DB_PASS" nextcloud < nextcloud_db.sql
For Rocket.Chat (PostgreSQL)
PostgreSQL handles hot-backups differently. Instead of mysqldump, you must use pg_dump. [1, 2]
The Backup Script Modification:
# Dump the PostgreSQL database (Notice the username syntax flag)
docker compose -f /path/to/dc-rocketchat/docker-compose.yml exec -T db \
pg_dump -U rocketchat_user -d rocketchat_db > /tmp_db/rocketchat_db.sql
# Run Restic snapshot
restic -r /mnt/user/backups/rocketchat-restic backup /mnt/user/appdata/rocketchat /tmp_db/rocketchat_db.sql
The Restore Command:
PostgreSQL restores utilize the standard application terminal router:
docker compose exec -T db psql -U rocketchat_user -d rocketchat_db < rocketchat_db.sql
