This is the least optional page in the Beginner tier. Everything else here is about adding capability; this one is about not losing what you've built.

The 3-2-1 rule, applied to a homelab

See 3-2-1 backup rule in the glossary for the general definition. In homelab terms:

  • 3 copies - the live data, plus at least two backups.
  • 2 different media - not two copies on the same disk. An external USB drive is a genuinely fine second copy for this tier.
  • 1 off-site - a cloud storage target (Backblaze B2, a relative's house with a drive, anything genuinely not in the same building) so a fire or theft doesn't take every copy at once.

You don't need all of this from day one, but know what you're missing. A single external drive plugged into the server is a backup, not the backup - it's better than nothing and it's not 3-2-1 yet.

What actually needs backing up

For a Docker-based homelab, it's narrower than people expect:

  • Bind-mounted volumes - the ./config, ./data style folders from your compose files (see Docker Compose Basics). This is where service state, databases, and media library metadata live.
  • The compose files themselves - small, text, trivial to back up, but easy to forget. Keep your ~/homelab/ folder (or wherever your compose files live) in the backup scope, ideally in a git repo too.
  • Anything not reproducible from the above - your actual media library, for instance, if re-acquiring it would be painful. Whether that's worth backing up (vs. just re-ripping/re-downloading) is a judgment call about how replaceable the content is.

You generally do not need to back up Docker images themselves - they're re-downloadable from their registry on demand.

Databases need a dump, not a file copy

The one trap in "just back up the bind-mounted folders": a database's data folder (the ./db-data from Docker Compose Basics, say) is being written to constantly while the container runs. Copy it mid-write and the backup finishes without complaint, but restoring it can give you a database that won't start. Two simple fixes:

  • Dump it first. Have the database write a consistent export into a folder that's in your backup scope, just before the backup runs. For the Postgres example from Docker Compose Basics:
    mkdir -p ~/homelab/app/db-dumps
    docker exec app-db pg_dump -U postgres postgres > ~/homelab/app/db-dumps/app.sql
    
    MariaDB/MySQL uses mariadb-dump instead - see Nextcloud for an example. Some apps do this for you: Immich writes its own scheduled database dumps into its library folder.
  • Or stop the stack while it backs up. docker compose stop, run the backup, docker compose start. A few minutes of downtime at 3am is a perfectly reasonable trade for a homelab.

A simple approach: restic to an external drive and B2

restic is a free, encrypted, deduplicating backup tool that works well for exactly this use case.

sudo apt install restic

Run the restic commands below as root (from a sudo -i shell, or from a root-owned systemd timer for the nightly run). Containers often create files your own user can't read - Postgres's data folder, for one - and a backup run as your normal user skips them with "permission denied." That's also why the paths below are written out in full: inside sudo -i, ~ means /root, not your home folder.

restic repositories are always encrypted and need a password. Set one before doing anything else, so it's available for the automated runs below rather than restic prompting interactively every time:

export RESTIC_PASSWORD="a-real-generated-password"

Save that same value somewhere durable (your password manager, not just this shell session) - a repository without its password is unrecoverable, the same way a Vaultwarden vault is gone for good if you forget its master password. For the cron job or systemd timer mentioned below, put it in an EnvironmentFile (systemd) or source it from a restricted-permission file rather than hardcoding it in the crontab itself.

Initialize a repository on an external drive:

restic init --repo /mnt/backup-drive/homelab-restic

Back up your homelab folder:

restic backup /home/yourname/homelab --repo /mnt/backup-drive/homelab-restic

Restic only stores changed data on each run (deduplication), so repeated backups after the first are fast and small. Automate it with a cron job or systemd timer running nightly - see cron/timer setup in your distro's documentation if you haven't used either before.

For the off-site copy, restic supports Backblaze B2, S3-compatible storage, and several others directly:

export B2_ACCOUNT_ID=xxx
export B2_ACCOUNT_KEY=xxx
restic init --repo b2:your-bucket-name:homelab
restic backup /home/yourname/homelab --repo b2:your-bucket-name:homelab

Test the restore before you need it

A backup you haven't restored from is a theory, not a backup. Once it's running, actually test a restore into a scratch folder:

restic restore latest --repo /mnt/backup-drive/homelab-restic --target /tmp/restore-test

Confirm the files look right. Do this now, while it's a five-minute exercise, not during an actual outage when it's a crisis.

With real backups in place, the last thing worth doing before calling this tier done is a sanity check on what it's actually costing you to run - see Cost and Power Tracking.

Next: Cost and Power Tracking.