Skip to main content

Backing Up Docker Containers: The Homelab Disaster You Can Avoid

Nobody thinks about Docker backups when everything is working.

The dashboard loads. The media server streams. The reverse proxy behaves. The database container has a cute little green “healthy” status. Life is good.

Then one day you run an update, delete the wrong volume, lose a disk, break a Compose file, or discover that the “temporary” container from six months ago was actually storing something important.

That is when Docker stops feeling magical and starts feeling like archaeology.

This post is a practical guide to backing up Docker containers on a Linux home server. Not enterprise disaster recovery. Not Kubernetes. Not a 40-page policy document. Just a realistic backup plan for a homelab running Docker Compose, bind mounts, named volumes and a few services that became more important than expected.

The main idea is simple:

You do not really back up containers. You back up the things needed to recreate them, plus the data they would destroy your weekend by losing.

If you are already working through the homelab security posts, this one fits right after Docker Security for Homelab Beginners, Linux Home Server Security Checklist, and UFW Firewall Rules for Home Servers.

1. The first rule: containers are disposable, data is not

A Docker container should be easy to recreate.

Your actual data is different.

Images can usually be pulled again. Containers can usually be rebuilt. Compose files can usually recreate the service.

But your database, uploaded files, application config, media metadata, monitoring history, password manager data, reverse proxy certificates, photo library or home automation state are not replaceable unless you backed them up.

Docker’s own documentation says volumes are the preferred mechanism for persisting data generated and used by containers, and that volumes are easier to back up or migrate than bind mounts.

That sentence is boring until you lose a volume.

So before you think about scripts, cron jobs or fancy tools, answer this:

If this server died tonight, what would I actually need to rebuild everything tomorrow?

2. Make a map of your Docker homelab

Start by listing what is running:

docker ps

Then include stopped containers:

docker ps -a

Show containers, images and exposed ports:

docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

If you use Docker Compose:

docker compose ls

Now create a small inventory. Nothing fancy. A text file is enough.

Service: Uptime Kuma
Folder: /srv/docker/uptime-kuma
Data: ./data
Database: SQLite inside data folder
Importance: high
Backup method: rsync folder while stopped or quiet

Service: Nginx Proxy Manager
Folder: /srv/docker/nginx-proxy-manager
Data: ./data and ./letsencrypt
Database: SQLite / MariaDB depending setup
Importance: high
Backup method: rsync folder + test restore

Service: PostgreSQL app
Folder: /srv/docker/postgres-app
Data: named volume postgres_data
Database: PostgreSQL
Importance: high
Backup method: pg_dump + volume archive

This inventory is the difference between having backups and having vague optimism.

3. Find where the data actually lives

Docker data usually lives in one of three places:

  • inside a bind mount;
  • inside a named volume;
  • inside the container filesystem, which is usually bad for important data.

Inspect a container:

docker inspect container-name

That output is long, so filter mounts if you have jq:

docker inspect container-name | jq '.[0].Mounts'

If you do not have jq:

sudo apt install jq

Example output:

[
  {
    "Type": "bind",
    "Source": "/srv/docker/jellyfin/config",
    "Destination": "/config"
  },
  {
    "Type": "volume",
    "Name": "postgres_data",
    "Destination": "/var/lib/postgresql/data"
  }
]

This tells you what must be backed up.

If the type is bind, the data is in a normal host folder.

If the type is volume, Docker manages it.

If your important data is only inside the container layer and not mounted anywhere, fix that soon. A recreated container may not keep it.

4. Bind mounts versus named volumes, without drama

Docker explains that a bind mount takes a file or directory from the host and mounts it into the container, while a volume is created inside Docker’s storage area and managed by Docker.

In homelabs, bind mounts are popular because they are easy to see:

volumes:
  - ./config:/config
  - ./data:/data

This is simple. You can back up the folder with rsync, tar, Borg, Restic, Kopia or whatever backup tool you like.

Named volumes look like this:

volumes:
  - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

These are cleaner in some ways and are managed by Docker, but you must remember to back them up explicitly.

My personal homelab preference:

  • Use bind mounts for simple apps where I want visible folders under /srv/docker.
  • Use named volumes when the image expects them or when Docker-managed storage makes sense.
  • Document both, because future me is forgetful.

The right answer is less important than knowing which answer your container uses.

5. Use a boring Docker folder structure

Boring folder structures are underrated.

I like this:

/srv/docker/
  uptime-kuma/
    docker-compose.yml
    .env
    data/

  nginx-proxy-manager/
    docker-compose.yml
    .env
    data/
    letsencrypt/

  jellyfin/
    docker-compose.yml
    config/
    cache/

  paperless/
    docker-compose.yml
    .env
    consume/
    data/
    media/
    export/

This makes backups easier because you can back up /srv/docker and capture most Compose files, environment files and bind-mounted data.

Example:

sudo rsync -aAX --info=progress2 /srv/docker/ /mnt/backup/homeserver/docker/

That one command is easy to understand six months later.

Compare that with data scattered across /opt, /home, /var, random named volumes and a few mystery folders created during a tutorial at 2 AM.

Do future you a favor: keep it boring.

6. Back up the Compose files first

Your docker-compose.yml files are the recipe book.

Without them, you may still have the data but no easy way to rebuild the service.

Back up:

  • docker-compose.yml
  • compose.yml
  • .env
  • override files
  • custom scripts
  • reverse proxy config
  • restore notes

Simple backup:

sudo tar -czf docker-compose-files-$(date +%F).tar.gz /srv/docker

Better regular backup:

sudo rsync -aAX --info=progress2 /srv/docker/ /mnt/backup/homeserver/docker/

Remember that .env files can contain secrets.

Protect them:

chmod 600 /srv/docker/*/.env

If you upload backups to cloud storage, encrypt them first.

7. Back up bind-mounted containers with rsync

If your containers use bind mounts under /srv/docker, backing them up is straightforward.

Example:

sudo rsync -aAX --info=progress2 /srv/docker/ /mnt/backup/homeserver/docker/

If you want a mirror backup:

sudo rsync -aAX --delete --info=progress2 /srv/docker/ /mnt/backup/homeserver/docker/

Be careful with --delete.

It makes the destination match the source. That is useful, but it also means deleted files on the source can disappear from the backup during the next run.

For important data, I prefer keeping dated snapshots or generations instead of only one mirrored copy.

Example dated archive:

sudo tar -czf /mnt/backup/homeserver/docker-$(date +%F).tar.gz /srv/docker

This is not elegant, but it is easy to restore and easy to understand.

8. Back up named Docker volumes

List volumes:

docker volume ls

Inspect a volume:

docker volume inspect volume-name

To back up a named volume, run a temporary container that mounts the volume and writes a tar archive.

docker run --rm \
  -v volume-name:/volume:ro \
  -v "$PWD":/backup \
  alpine \
  tar -czf /backup/volume-name-$(date +%F).tar.gz -C /volume .

Replace volume-name with your actual volume.

Example:

docker run --rm \
  -v postgres_data:/volume:ro \
  -v "$PWD":/backup \
  alpine \
  tar -czf /backup/postgres_data-$(date +%F).tar.gz -C /volume .

Then move the archive to your backup disk:

sudo mkdir -p /mnt/backup/homeserver/docker-volumes
sudo mv postgres_data-*.tar.gz /mnt/backup/homeserver/docker-volumes/

The :ro mount makes the volume read-only for the backup container. That is a nice habit.

9. Restore a named volume before you need to

Create a new volume:

docker volume create restored_postgres_data

Extract the archive into it:

docker run --rm \
  -v restored_postgres_data:/volume \
  -v "$PWD":/backup \
  alpine \
  sh -c "cd /volume && tar -xzf /backup/postgres_data-2026-05-19.tar.gz"

Then inspect it:

docker run --rm \
  -v restored_postgres_data:/volume:ro \
  alpine \
  ls -lah /volume

This is the boring test that proves your backup is not just decorative.

Do not wait for a dead disk to learn the restore command.

10. Databases need special treatment

File-level backups are not always enough for databases.

If a database is actively writing while you copy its files, your backup may be inconsistent. For databases, use database-aware backup tools where possible.

PostgreSQL’s official documentation says pg_dump can output script or archive formats, and script dumps contain the SQL commands needed to reconstruct the database to the saved state.

PostgreSQL example

Back up one database:

docker exec postgres-container \
  pg_dump -U postgres database_name > database_name-$(date +%F).sql

Back up all databases and global objects with pg_dumpall:

docker exec postgres-container \
  pg_dumpall -U postgres > postgres-all-$(date +%F).sql

Move the dump:

sudo mkdir -p /mnt/backup/homeserver/database-dumps
sudo mv postgres-all-*.sql /mnt/backup/homeserver/database-dumps/

MariaDB / MySQL example

docker exec mysql-container \
  mysqldump -u root -p database_name > database_name-$(date +%F).sql

You may be prompted for the database password.

SQLite example

Many small self-hosted apps use SQLite. For simple homelab backups, stop the app first:

cd /srv/docker/app-name
docker compose down
cp data/app.db /mnt/backup/homeserver/database-dumps/app.db-$(date +%F)
docker compose up -d

If the application has a built-in export feature, use that too.

For important databases, I like both:

  • a logical database dump;
  • a backup of the container data directory or volume.

One gives you clean restore options. The other gives you more context if something weird happens.

11. Stop containers when consistency matters

For a home server, downtime is often acceptable.

If a service writes constantly and you do not have snapshots or app-aware backups, stop it before backing up the files.

cd /srv/docker/service-name
docker compose down
sudo tar -czf /mnt/backup/homeserver/service-name-$(date +%F).tar.gz .
docker compose up -d

For multiple stacks:

cd /srv/docker
docker compose down
sudo rsync -aAX ./ /mnt/backup/homeserver/docker/
docker compose up -d

Only do that if your Compose file controls the services you intend to stop. Do not accidentally stop unrelated services because you ran the command from the wrong directory.

Check first:

docker compose ps

12. Back up before updates, not after panic

Before updating important containers:

cd /srv/docker/service-name

docker compose ps
docker compose logs --tail=50

sudo tar -czf /mnt/backup/homeserver/service-name-before-update-$(date +%F).tar.gz .

docker compose pull
docker compose up -d

docker compose logs --tail=100

This is especially important for:

  • password managers;
  • photo libraries;
  • home automation;
  • reverse proxies;
  • databases;
  • monitoring tools;
  • anything other people in the house use.

Updating without a backup is not bravery. It is gambling with extra steps.

13. Create a simple backup script

Start simple. A backup script you understand is better than a beautiful script you are afraid to edit.

#!/bin/bash
set -euo pipefail

SOURCE="/srv/docker/"
DEST="/mnt/backup/homeserver/docker/"
LOG="/var/log/docker-homelab-backup.log"
DATE="$(date +%F)"

echo "[$DATE] Starting Docker folder backup" | tee -a "$LOG"

mkdir -p "$DEST"

rsync -aAX --info=progress2 "$SOURCE" "$DEST" | tee -a "$LOG"

echo "[$DATE] Docker folder backup complete" | tee -a "$LOG"

Save it:

sudo nano /usr/local/sbin/backup-docker-homelab.sh

Make it executable:

sudo chmod +x /usr/local/sbin/backup-docker-homelab.sh

Run it manually first:

sudo /usr/local/sbin/backup-docker-homelab.sh

Only schedule it after it works.

14. Schedule the backup with cron

Open root’s crontab:

sudo crontab -e

Example weekly backup every Sunday at 03:30:

30 3 * * 0 /usr/local/sbin/backup-docker-homelab.sh >> /var/log/docker-homelab-backup.log 2>&1

Check the log:

sudo tail -100 /var/log/docker-homelab-backup.log

Check the destination:

ls -lah /mnt/backup/homeserver/docker/
du -sh /mnt/backup/homeserver/docker/

A scheduled backup that nobody checks is just a scheduled assumption.

15. Keep backup generations

A single mirror backup is useful, but it has a weakness: it can faithfully mirror a bad state.

If a file was corrupted yesterday and your backup ran last night, congratulations, you may now have a perfect backup of corrupted data.

Keep generations.

Simple dated archive approach:

sudo tar -czf /mnt/backup/homeserver/docker-$(date +%F).tar.gz /srv/docker

Example result:

docker-2026-05-19.tar.gz
docker-2026-05-26.tar.gz
docker-2026-06-02.tar.gz

Practical homelab retention:

  • last 7 daily backups for important services;
  • last 4 weekly backups;
  • monthly backup for things you really care about.

You do not need infinite history. You need enough history to recover from mistakes you do not notice immediately.

16. Store backups away from the original disk

If your backup is on the same disk as the original data, it is better than nothing but not enough.

Better options:

  • external USB disk;
  • another Linux machine;
  • NAS;
  • encrypted cloud storage;
  • offsite disk stored somewhere safe.

Example backup to another machine:

rsync -aAX /srv/docker/ backupuser@192.168.1.50:/backups/homeserver/docker/

Example backup to USB disk:

rsync -aAX /srv/docker/ /mnt/usb-backup/homeserver/docker/

If you send backups to cloud storage, remember that Compose folders and .env files may include secrets.

Encrypt first.

17. Test restore like a boring professional

This is the part most people skip.

Do not skip it.

Create a temporary restore test:

mkdir -p /tmp/docker-restore-test
cd /tmp/docker-restore-test
tar -xzf /mnt/backup/homeserver/service-name-2026-05-19.tar.gz
ls -lah

If there is a Compose file, inspect it:

cat docker-compose.yml

If the service can safely be started on a different port, test it:

docker compose up -d
docker compose ps
docker compose logs --tail=100

For a database dump:

ls -lh postgres-all-2026-05-19.sql
head postgres-all-2026-05-19.sql

For a named volume archive:

tar -tzf postgres_data-2026-05-19.tar.gz | head

Testing restore is not exciting. That is why it works.

Untested backups are just files with self-confidence.

18. Document the restore process

Add a small RESTORE.md file to each important service folder.

# Restore notes for Uptime Kuma

1. Copy this folder to /srv/docker/uptime-kuma
2. Check .env permissions if present
3. Run: docker compose pull
4. Run: docker compose up -d
5. Check: docker compose logs --tail=100
6. Open: http://server-ip:3001
7. Confirm monitors and notification settings exist

This does not need to be beautiful.

It needs to be understandable when you are tired, annoyed and trying to recover something that should not have broken.

19. Be careful with cleanup commands

Docker cleanup commands are useful, but they can remove things you care about.

Usually safe:

docker image prune

More serious:

docker system prune

Potentially painful:

docker volume prune

Before pruning volumes:

docker volume ls
docker volume inspect volume-name

If you are not sure, back up the volume first.

The phrase “unused volume” does not always mean “unimportant volume”. Sometimes it means “the container that used this is currently gone because I was testing something”.

That is exactly how homelab disasters are born.

20. A practical backup layout

I like this kind of structure:

/mnt/backup/
  homeserver/
    docker/
      compose-folders/
      named-volumes/
      database-dumps/
      before-updates/
      restore-tests/
    etc/
    home/
    logs/

Example commands:

sudo mkdir -p /mnt/backup/homeserver/docker/compose-folders
sudo mkdir -p /mnt/backup/homeserver/docker/named-volumes
sudo mkdir -p /mnt/backup/homeserver/docker/database-dumps
sudo mkdir -p /mnt/backup/homeserver/docker/before-updates

This layout separates different types of backups:

  • Compose folders recreate services.
  • Named volume archives protect Docker-managed data.
  • Database dumps restore databases cleanly.
  • Before-update archives help undo bad upgrades.

When you need to restore, clarity matters.

21. The monthly Docker backup drill

Once a month, run a small drill:

docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
docker volume ls
docker system df
sudo du -sh /srv/docker
sudo du -sh /mnt/backup/homeserver/docker
sudo tail -100 /var/log/docker-homelab-backup.log

Then ask:

  • Did any new container appear?
  • Did I add a new named volume?
  • Are database dumps still being created?
  • Is the backup disk full?
  • Have I tested a restore recently?
  • Are .env files protected?
  • Are backups stored away from the original disk?

This pairs nicely with Lynis Hardening Checklist and Fail2ban for Beginners: security is not only about blocking attackers, it is also about recovering when something goes wrong.

22. My simple Docker backup checklist

For every important Docker service, I want to know:

  • Where is the Compose file?
  • Where is the .env file?
  • Does it use bind mounts?
  • Does it use named volumes?
  • Does it use PostgreSQL, MySQL, MariaDB or SQLite?
  • Can I safely stop it during backup?
  • Do I need a database dump?
  • Where is the backup stored?
  • How many backup generations exist?
  • When did I last test restore?

If you can answer those questions, you are already ahead of many homelab setups.

23. Common mistakes that ruin Docker backups

Only backing up the container

The container is usually disposable. The data is not.

Forgetting named volumes

Backing up /srv/docker may not include Docker-managed named volumes.

Copying live databases blindly

Use database dumps or stop the service first when consistency matters.

Keeping only one mirrored backup

A mirror can copy corruption, deletion or mistakes.

Never testing restore

This is the classic one. The backup looks good until you need it.

Leaving secrets readable

Backups often contain .env files, passwords, tokens and certificates. Protect them.

Running docker volume prune with confidence

Confidence is not a backup strategy.

Final thoughts

Backing up Docker containers is not complicated once you stop thinking about containers as the important part.

The important part is the recipe and the data:

  • Compose files;
  • .env files;
  • bind-mounted folders;
  • named volumes;
  • database dumps;
  • restore notes;
  • tested recovery steps.

A good homelab backup plan does not need to be fancy.

It needs to be boring, repeatable and tested.

Because when a disk dies or an update breaks something, you do not want to discover that your “backup strategy” was actually just hope with a cron job.

Do the boring work now.

Future you, standing in front of a broken Docker host, will be very grateful.

Related homelab security posts

Comments

Popular posts from this blog

Honeypot deployment on Linux - OpenCanary

What’s a honeypot what what it’s purpose ? It’s basically a computer or Virtual Machine emulating some services (ex: ssh, ftp, telnet, netbios, https, samba server etc) and accepting, logging and sending warnings of all incoming connections. You can use it as intrusion detection or early warning system but it also might go a little further and allow one to get inside the intruders ”head” since you get to log every interaction. How and where should it be placed? Let’s start with “where”. I usually place them in specific areas to get an idea how/or if the network is tested from outside or inside. So I have about three major areas; behind firewalls, in “sensible zones” where only pre-defined machines should have access and in the “public zone” such as administrative/general network. Placing a honeypot behind firewalls/”sensible zones” will ensure that the firewall is doing it’s and if you get a hit that means you have a miss-configurations or a serious intrusion. Honeypots place...

Lenovo X250 tweeking in linux

Why the Lenovo X250? My needs: a daily driver laptop, very cheap, light, small, upgradable and serviceable. (want to swap hdd, thermal paste etc) and Linux friendly. All things considered, I came up with a bargain on eBay, a 12.5"  Lenovo ThinkPad X250, i5 5300U with 8gb RAM, 128gb SSD, 2 batteries and HD screen with a barely noticeable bruise (which shall be swapped latter) for 130Euro. X250 condition X250 keyboard and screen The Lenovo X250 in 2019 It's preparation for linux daily driver. The batteries, yep no typo --two batteries-- this model has 2, one internal the other external were ~82% capacity each, the screen bruise is somehow noticeable, the fan and thermals were alright, yet first things to do; swap thermal paste for a top of the line one and swap the 128gb SSD for one bigger a 256SSD.  Keep in mind that there's a whitelist of LCD screens, if not on the list no brightness control on windows. So price tag till now: X250 + Postage = 130Euro Gri...

Strong unique passwords

Strategies for creating strong, unique passwords and properly managing them: As more and more of our daily activities and personal information are being conducted and stored online, it's crucial to have strong, unique passwords for all of your online accounts. Not only do strong passwords protect your personal information from potential hackers, but they can also prevent unauthorized access to your accounts and protect your privacy. But with so many different online accounts and passwords to remember, it can be tempting to use the same password for multiple accounts or to create passwords that are easy to remember but not very secure. This is a dangerous practice, as it puts all of your accounts at risk if one password is compromised. Here are some tips for creating strong, unique passwords and properly managing them: Use a mix of letters, numbers, and special characters in your passwords. Avoid using dictionary words or common phrases, as these can be easily guessed or cracked by ...