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

  1. 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).
  2. Extract the .bz2 file on your computer to get the raw restic binary file.
  3. Plug your Unraid flash drive into your computer (or access it over the network via the boot SMB share).
  4. Create a folder named extra on the flash drive if it doesn’t exist, and drop the restic file 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]

  1. Open /boot/config/go on your flash drive using a text editor (like Notepad++ or VS Code).
  2. 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
  1. 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 nice and ionice to 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

Leave a Reply