Skip to content

Back up and restore Repod

Repod stores all of its state in Docker volumes mounted under /repos/. This guide explains what to back up, how to run and schedule backup.sh, how to copy backups offsite, and how to perform a full restore.


1. What to back up

Data Path Importance Notes
User database /repos/auth/users.db Critical SQLite WAL-mode database. Always use sqlite3 .backup or the backup.sh script for a WAL-safe copy — never cp a live SQLite file.
Settings /repos/settings.json Critical All application configuration: LDAP, rate limits, allowed distributions, etc.
Audit logs /repos/audit/*.jsonl High Append-only JSONL files, one per day. Needed for compliance and incident investigation.
GPG keyring /repos/gnupg/ High Contains the private signing key. Loss means generating a new key and reconfiguring every client machine.
Package manifests /repos/manifests/ Medium JSON metadata for each package. Can be rebuilt by re-indexing the package pool, but this takes time.
.deb package pool /repos/pool/ Optional Large. Can be rebuilt by re-uploading or re-importing packages from upstream. Back up if rebuilding would be impractical.

GPG keyring loss is painful

If you lose the GPG keyring without a backup, you must generate a new signing key and distribute it to every client machine that trusts your repository. This is a significant operational burden in large fleets. Treat the gnupg/ directory as critical infrastructure.


2. Using backup.sh

The backup.sh script is included at the root of the Repod repository. It performs a WAL-safe SQLite backup and archives all critical paths into a timestamped .tar.gz file.

Basic usage:

cd /home/vagrant/repod-data/repodata
./backup.sh

By default, the archive is written to ./backups/ in the current directory. The archive name includes a UTC timestamp:

backups/repod-backup-2026-05-13T14-30-00Z.tar.gz

Configuration via environment variables:

Variable Default Description
BACKUP_DIR ./backups Directory where archive files are written.
BACKUP_RETENTION_DAYS 30 Archives older than this many days are deleted automatically. Set to 0 to disable pruning.

Example with custom paths:

BACKUP_DIR=/mnt/nas/repod-backups \
BACKUP_RETENTION_DAYS=90 \
./backup.sh

Dry run — print what would be backed up without creating an archive:

DRY_RUN=1 ./backup.sh

Note

backup.sh must be run on the host where the Docker volumes are mounted. It does not require the Repod containers to be stopped — the SQLite backup command is safe against concurrent writes.


3. Scheduling with cron

Run backups automatically by adding an entry to the crontab of the user who owns the Repod volumes:

crontab -e

Add a daily backup at 02:00 UTC, logging output to a file:

# Repod daily backup — 02:00 UTC
0 2 * * * BACKUP_DIR=/mnt/nas/repod-backups BACKUP_RETENTION_DAYS=60 \
  /home/vagrant/repod-data/repodata/backup.sh \
  >> /var/log/repod-backup.log 2>&1

Verify the cron job is registered:

crontab -l

Check the log after the first scheduled run:

tail -50 /var/log/repod-backup.log

Tip

Rotate the log file with logrotate to prevent it from growing unbounded. Create /etc/logrotate.d/repod-backup with a weekly rotation and 4-week retention policy.


4. Offsite copy

A backup that lives on the same host as the data it protects is not a real backup. Copy archives to a separate location after each run.

# Add this after ./backup.sh in your cron job, or in a wrapper script
rsync -avz --delete \
  /mnt/nas/repod-backups/ \
  nas-user@nas.example.com:/volume1/backups/repod/

Use SSH key authentication so this runs unattended. Restrict the NAS user to SFTP and the backup directory.

Install rclone and configure a remote named s3-backup:

rclone config
# Follow prompts: select "s3" provider, enter endpoint, access key, secret key

Then sync after each backup run:

rclone sync \
  /mnt/nas/repod-backups/ \
  s3-backup:your-bucket/repod/ \
  --s3-storage-class STANDARD_IA \
  --log-level INFO

A combined cron entry:

0 2 * * * \
  BACKUP_DIR=/mnt/nas/repod-backups BACKUP_RETENTION_DAYS=60 \
  /home/vagrant/repod-data/repodata/backup.sh \
  && rclone sync /mnt/nas/repod-backups/ s3-backup:your-bucket/repod/ \
  >> /var/log/repod-backup.log 2>&1

Tip

For S3-compatible targets, enable object versioning or object lock on the bucket so that a ransomware event on the Repod host cannot overwrite your offsite copies.


5. Restore procedure

Follow these steps to restore Repod from a backup archive. Read through the entire procedure before you start.

Step 1 — Stop the stack

cd /home/vagrant/repod-data/repodata
docker compose down

Step 2 — Identify the archive to restore

ls -lht /mnt/nas/repod-backups/
# Pick the most recent clean backup, e.g.:
# repod-backup-2026-05-12T02-00-00Z.tar.gz

Step 3 — Extract the archive

mkdir -p /tmp/repod-restore
tar -xzf /mnt/nas/repod-backups/repod-backup-2026-05-12T02-00-00Z.tar.gz \
    -C /tmp/repod-restore
ls /tmp/repod-restore/

Step 4 — Copy files back to Docker volumes

The volume mount paths depend on your docker-compose.yaml. Adjust the destination paths to match your setup:

# User database
cp /tmp/repod-restore/users.db /repos/auth/users.db

# Settings
cp /tmp/repod-restore/settings.json /repos/settings.json

# Audit logs (merge, not overwrite, to preserve any logs written since the backup)
cp -n /tmp/repod-restore/audit/*.jsonl /repos/audit/

# GPG keyring
rm -rf /repos/gnupg/
cp -r /tmp/repod-restore/gnupg/ /repos/gnupg/
chmod 700 /repos/gnupg/

# Manifests
cp -r /tmp/repod-restore/manifests/ /repos/manifests/

Warning

Set the correct ownership on restored files before starting the containers. If the backend container runs as a non-root user (UID 1000 by default), ensure the restored files are owned by that UID: chown -R 1000:1000 /repos/

Step 5 — Start the stack

docker compose up -d

Step 6 — Verify

# Check all containers are healthy
docker compose ps

# Confirm the API responds
curl -s http://localhost:8000/health | jq .

# Confirm packages are visible
curl -s http://localhost:8000/packages/?distribution=jammy | jq '.total'

# Test apt update on a client machine
sudo apt update

6. Testing restores

A backup you have never tested is a backup you cannot trust.

Test restores quarterly using this checklist:

  • Spin up a separate VM or container with a clean Docker installation.
  • Copy the most recent backup archive to the test VM.
  • Follow the restore procedure above on the test VM.
  • Confirm the API returns the expected package count.
  • Confirm apt update succeeds on a machine pointed at the test instance.
  • Confirm the web UI is accessible and settings look correct.
  • Document the time taken from archive copy to verified restore.

Record the results in your operations runbook. If a restore takes longer than your RTO (recovery time objective), investigate whether the archive size, network speed, or procedure can be optimised.


7. GPG keyring warning

Lost GPG backup = mandatory key rotation

If your restore fails because the GPG keyring backup is missing or corrupted, you cannot recover the original signing key. You must:

  1. Generate a new GPG key via Settings → GPG → Generate key.
  2. Distribute the new public key to every client machine.
  3. Remove the old key from every client machine's /etc/apt/trusted.gpg.d/.

This is a significant effort in large fleets. See Rotate GPG signing keys for the full procedure. Preventing this situation by verifying your gnupg/ backup is included in every archive is strongly recommended.