DockerSelf-Hosting

restic with Docker: Back Up Docker Volumes to a Local Disk, NAS or S3 (Linux, Windows, Mac)

Before you start

  • 22 min read
  • 35 to run in terminal
  • 3 file contents

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

restic is an open-source backup program that encrypts your data, stores it in a repository on a local disk, a NAS or a cloud bucket, and keeps one snapshot per run so you can restore any earlier version.

restic official logo

Logo: the official restic project (github.com/restic/restic, file doc/logo/logo.png)

Docker volumes hold the data of your self-hosted apps: notes, photos, passwords, databases. A volume is not a backup. If the disk fails or the volume is deleted, the data is gone. restic gives you a second copy that is encrypted before it leaves the machine, skips data it has already stored (de-duplication), and can be tested by restoring into an empty folder.

This guide runs restic as a Docker container. You back up one volume, stop or dump its database first, check the repository, and restore a test copy. The one thing you must not lose is the repository password: without it the data is irrecoverably lost.

WhatValue
Latest releaserestic 0.19.1, released July 2026 (release page)
Version used here0.18.1 (September 2025). We pin it because two open reports on 0.19.1 hit the prune and check commands (see Known bugs)
Time neededAbout 30 to 45 minutes for the first run (our estimate)
RAM and sizeNot documented by the project; not measured by us
PortsNone. restic does not listen on a network port. The REST server option uses port 8000
LicenceBSD 2-Clause (code in the LICENSE file)
Official docsrestic.readthedocs.io

At a glance:

  • What it does: backs up folders and command output into an encrypted repository, one snapshot per run.
  • Where the data can go: a local folder, a NAS or SFTP server, S3 and other cloud storage, or a REST server you run yourself.
  • What it does not do: it has no scheduler and no web interface. You run it, or a scheduled job runs it.
  • Your job: a strong repository password, a stopped or dumped database, a restore test, and an upgrade plan.
  • Platforms: the commands below run on Linux, Windows (PowerShell) and Mac (Terminal) through Docker Desktop on Windows and Mac.

How it fits together#

Your volume is read only, restic encrypts and uploads the data, and the repository is the only place the backup lives.

Flow diagram of a restic backup of a Docker volume: you start the backup, the app is stopped or its database dumped first, the Docker volume is mounted read only, restic reads and encrypts the files, the encrypted packs go to the repository on a local disk, NAS or S3, and a restore test brings data back into an empty folder

Drawn for this guide. The arrows follow the restic docs: data is encrypted before it is uploaded, and a file that cannot be read gives exit code 3 while the snapshot is still saved.

Before you start#

You need Docker and a place for the repository that is not the same disk as the data you back up.

SystemWhat you needNotes
LinuxDocker Engine, a bash shellThe docker commands run as your user (add yourself to the docker group, or use sudo)
WindowsDocker Desktop with Linux containers, PowerShellUse PowerShell 7 if you can. The folders live under $HOME\docker_volumes
MacDocker Desktop for Mac, TerminalSame commands as Linux

Check Docker first:

Run in terminalbash
docker --version
Run in terminalpowershell
docker --version

Checklist before you start:

  • [ ] Docker runs and docker --version prints a version
  • [ ] You know the name of the volume to back up (docker volume ls lists them)
  • [ ] The repository goes to a different disk, NAS or bucket than the data
  • [ ] You have a password manager, or another safe place, for the repository password
  • [ ] For a database: you know its dump command from the database's own documentation

Tested by DIY Lab Hub: not yet. Nothing in this guide was run for it. Every command and value comes from the official restic and Docker documentation, read in October 2026. The steps and the expected outputs are marked as such.

We use the example volume notes_data (used by a container called notes). Replace those names with your own.

Step 1: Pull the pinned restic image#

The image is published by the restic project to the GitHub container registry. The project's release workflow tags every release with its version number, so we pin 0.18.1. The tag latest also exists, but it changes; a pinned tag does not.

Linux#

Run in terminalbash
docker pull ghcr.io/restic/restic:0.18.1
docker run --rm ghcr.io/restic/restic:0.18.1 version

Windows (PowerShell)#

Run in terminalpowershell
docker pull ghcr.io/restic/restic:0.18.1
docker run --rm ghcr.io/restic/restic:0.18.1 version

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
docker pull ghcr.io/restic/restic:0.18.1
docker run --rm ghcr.io/restic/restic:0.18.1 version

Check it worked. The version command prints one line. Expected output shape (the restic docs show this format; the Go version and platform on your machine will differ):

Run in terminaltext
restic 0.18.1 compiled with go<version> on linux/amd64

Tip: Pin the tag, not latest. A backup tool that changes under you is the one you find out about after a restore fails.

Step 2: Create the folders and the password file#

We keep everything for this backup under ~/docker_volumes/restic. The repository goes into backups, the password file into config, and logs into logs. The password file holds the only key to your data, so it is created with permissions that only you can read.

The restic docs say a password on the command line, or in an inline RESTIC_PASSWORD=... prefix, can show up in the process list. A file is the safer route.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/restic/{backups,config,logs}
(umask 077; read -rs -p "Repository password: " PW; echo; printf '%s' "$PW" > ~/docker_volumes/restic/config/restic-password.txt)
unset PW
chmod 600 ~/docker_volumes/restic/config/restic-password.txt

Windows (PowerShell)#

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\restic\backups", "$HOME\docker_volumes\restic\config", "$HOME\docker_volumes\restic\logs" | Out-Null
$secure = Read-Host "Repository password" -AsSecureString
$plain = [System.Net.NetworkCredential]::new('', $secure).Password
Set-Content -NoNewline -Path "$HOME\docker_volumes\restic\config\restic-password.txt" -Value $plain
Remove-Variable plain, secure
icacls "$HOME\docker_volumes\restic\config\restic-password.txt" /inheritance:r /grant:r "${env:USERNAME}:F"

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
mkdir -p ~/docker_volumes/restic/{backups,config,logs}
(umask 077; read -rs -p "Repository password: " PW; echo; printf '%s' "$PW" > ~/docker_volumes/restic/config/restic-password.txt)
unset PW
chmod 600 ~/docker_volumes/restic/config/restic-password.txt

Check it worked. The file exists and only your user can read it. On Linux and Mac:

Run in terminalbash
ls -l ~/docker_volumes/restic/config/restic-password.txt

Expected output shape (owner and size will differ; the permissions must be -rw-------):

Codetext
-rw------- 1 <your-user> <your-group> 25 <date> /home/<your-user>/docker_volumes/restic/config/restic-password.txt

Tip: Write the password down in a second place you control before the first backup. Losing it means the backup cannot be opened, and nobody can reset it for you.

Step 3: Create the repository#

The repository is a folder full of encrypted files. You create it once with init. restic refuses to overwrite an existing repository, so a second init on the same folder does nothing harmful.

The container sees the repository as /repo and the password file as /pass. The --hostname flag is set on purpose: restic uses the host name for some of its operations, and Docker would otherwise give each new container a random one.

Linux#

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 init

Windows (PowerShell)#

Run in terminalpowershell
docker run --rm --hostname docker-backup `
  -v "$HOME\docker_volumes\restic\backups:/repo" `
  -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" `
  -e RESTIC_REPOSITORY=/repo `
  -e RESTIC_PASSWORD_FILE=/pass `
  ghcr.io/restic/restic:0.18.1 init

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 init

Check it worked. The output names the new repository. The restic docs show this shape (the ID and path on your machine will differ):

Run in terminaltext
created restic repository 085b3c76b9 at /repo
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.

Running init twice on the same folder is refused, which is the safety check working.

Tip: The restic image does not set a non-root user (its Dockerfile has no USER line). On Linux, files it writes into a bind mount are owned by root. Plan to use sudo to delete or move them by hand.

Step 4: Stop the app or dump its database#

This is the step people skip. restic copies whatever is on disk at the moment it reads it. A database that is still writing can give you files that do not match each other, and a restore may start with a corrupt database. The fix is to make a consistent copy first.

There are two routes:

  1. Simple apps: stop the container for the few minutes the backup takes, then start it again.
  2. Databases: dump the database to a file with its own dump tool, check that the dump worked, and back up the dump folder.

The restic docs show the dump pattern with mysqldump. They also warn that a pipe into restic backup --stdin hides a failed dump and can save an empty backup. The fix the docs give is to check the exit code yourself and act on it. The script below does exactly that: if the dump fails, it stops before restic runs.

Linux#

Run in terminalbash
docker stop notes
# ... run step 5, then:
docker start notes

For a database, dump into a folder that is backed up (replace the placeholder with the dump command from your database's documentation):

Run in terminalbash
mkdir -p ~/docker_volumes/notes-db/backups
docker exec notes-db <dump command from the database docs> > ~/docker_volumes/notes-db/backups/notes-db.sql || { echo "Dump failed - backup stopped"; exit 1; }

Windows (PowerShell)#

Run in terminalpowershell
docker stop notes
# ... run step 5, then:
docker start notes

For a database:

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\notes-db\backups" | Out-Null
docker exec notes-db <dump command from the database docs> > "$HOME\docker_volumes\notes-db\backups\notes-db.sql"
if ($LASTEXITCODE -ne 0) { throw "Dump failed - backup stopped" }

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
docker stop notes
# ... run step 5, then:
docker start notes

For a database:

Run in terminalbash
mkdir -p ~/docker_volumes/notes-db/backups
docker exec notes-db <dump command from the database docs> > ~/docker_volumes/notes-db/backups/notes-db.sql || { echo "Dump failed - backup stopped"; exit 1; }

Check it worked. After the stop, the container is not running, and after the start it is:

Run in terminalbash
docker ps --filter name=notes

Expected output shape (the docs of Docker show this format; your columns and IDs will differ):

Codetext
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

If the container is not listed, it is stopped. Start it with docker start notes.

Tip: Stop the database container itself, or dump it, before the backup. Do not back up a running database's data folder and hope for the best.

Step 5: Back up the volume#

Now you back up the volume. The restic container gets the volume read only (:ro), so a backup cannot change your data. The repository and the password file are mounted the same way as in step 3.

Linux#

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v notes_data:/data:ro \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 backup /data --tag notes

Windows (PowerShell)#

Run in terminalpowershell
docker run --rm --hostname docker-backup `
  -v notes_data:/data:ro `
  -v "$HOME\docker_volumes\restic\backups:/repo" `
  -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" `
  -e RESTIC_REPOSITORY=/repo `
  -e RESTIC_PASSWORD_FILE=/pass `
  ghcr.io/restic/restic:0.18.1 backup /data --tag notes

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v notes_data:/data:ro \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 backup /data --tag notes

Check it worked. The last lines of the output show the snapshot. The restic docs show this shape:

Run in terminaltext
snapshot 40dc1520 saved

Exit codes matter. 0 means every file was read. 3 means some source files could not be read, and restic still saved a snapshot with the rest. 1 means a fatal error and no snapshot. Check the exit code in scripts (Linux and Mac: echo $?; PowerShell: $LASTEXITCODE).

Tip: Use --dry-run (short form -n) on a new backup command first. It reports what would be done without writing to the repository.

Step 6: List the snapshots and check the repository#

Next, confirm that the snapshot is in the repository and that the repository is sound.

Linux#

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 snapshots

docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 check

Windows (PowerShell)#

Run in terminalpowershell
docker run --rm --hostname docker-backup `
  -v "$HOME\docker_volumes\restic\backups:/repo" `
  -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" `
  -e RESTIC_REPOSITORY=/repo `
  -e RESTIC_PASSWORD_FILE=/pass `
  ghcr.io/restic/restic:0.18.1 snapshots

docker run --rm --hostname docker-backup `
  -v "$HOME\docker_volumes\restic\backups:/repo" `
  -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" `
  -e RESTIC_REPOSITORY=/repo `
  -e RESTIC_PASSWORD_FILE=/pass `
  ghcr.io/restic/restic:0.18.1 check

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 snapshots

docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 check

Check it worked. snapshots lists one line per snapshot, with its ID, time, host, tags and paths. The restic docs show the table shape. check without options is quick: it checks the structure of the repository without downloading all the data. The restic docs say check ends with no errors were found when the repository is sound.

Tip: check --read-data downloads the whole repository to verify every file. It takes much longer and costs bandwidth on cloud storage. Run it on a schedule you can afford, and keep the result.

Step 7: Restore into an empty folder#

A backup you have never restored is a hope, not a backup. Restore into a new empty folder, never over your live volume.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/restic/restore-test
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -v ~/docker_volumes/restic/restore-test:/restore \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 restore latest --target /restore
ls -R ~/docker_volumes/restic/restore-test | head -20

Windows (PowerShell)#

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\restic\restore-test" | Out-Null
docker run --rm --hostname docker-backup `
  -v "$HOME\docker_volumes\restic\backups:/repo" `
  -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" `
  -v "$HOME\docker_volumes\restic\restore-test:/restore" `
  -e RESTIC_REPOSITORY=/repo `
  -e RESTIC_PASSWORD_FILE=/pass `
  ghcr.io/restic/restic:0.18.1 restore latest --target /restore
Get-ChildItem -Recurse "$HOME\docker_volumes\restic\restore-test" | Select-Object -First 20

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
mkdir -p ~/docker_volumes/restic/restore-test
docker run --rm --hostname docker-backup \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -v ~/docker_volumes/restic/restore-test:/restore \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  ghcr.io/restic/restic:0.18.1 restore latest --target /restore
ls -R ~/docker_volumes/restic/restore-test | head -20

Check it worked. The files of the volume appear under the restore folder, below the path restic saved them under (/data, so the files sit in restore-test/data). Open a few files and compare them with the live volume. Then delete the restore folder. The restic docs say --target should be a new, non-existing folder, or you should add --overwrite never.

Put it behind HTTPS (with or without Traefik)#

restic itself is a command-line program and has no web page, so there is nothing to put behind HTTPS on the same machine. HTTPS matters in one case: a repository served over the network by the REST server, the separate restic project rest-server. restic reads a REST repository with rest:https://.... Choose this only if you want a remote repository on your own server. For a home NAS on the same network, the local folder backend is simpler. Local-only users can skip this section.

The REST server stores the same folder layout as the local backend (restic docs), so you can serve the backups folder from step 3. Its latest release is 0.14.0 (May 2025), licence BSD 2-Clause, image restic/rest-server:0.14.0, port 8000. Users are kept in an .htpasswd file, created with the create_user command inside the container (docker exec -it rest-server create_user <name>). Never run it without a password and without HTTPS on the internet.

Two routes, both end with HTTPS.

With Traefik#

Traefik needs a domain name pointing at your server, ports 80 and 443 open, and an external Docker network called proxy. Create the network once if it does not exist:

Run in terminalbash
docker network create proxy

Save this as ~/docker_volumes/restic/docker-compose.yml (no version: line is needed), replacing <your-domain>:

File contentyaml
services:
  rest-server:
    image: restic/rest-server:0.14.0
    container_name: rest-server
    restart: unless-stopped
    command: ["--path", "/data"]
    volumes:
      - ~/docker_volumes/restic/backups:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.restic.rule=Host(`restic.<your-domain>`)
      - traefik.http.routers.restic.entrypoints=https
      - traefik.http.routers.restic.tls=true
      - traefik.http.services.restic.loadbalancer.server.port=8000

networks:
  proxy:
    external: true

Start it with docker compose -f ~/docker_volumes/restic/docker-compose.yml up -d, then create a user as above. The clients then use RESTIC_REPOSITORY=rest:https://<user>:<password>@restic.<your-domain>/. The entrypoints=https name must match your Traefik setup.

Without Traefik: Caddy#

Caddy is a web server that gets certificates for you. Choose it when you have no Traefik.

Your caseWhat Caddy doesCaddyfile
Public domain, DNS A record to your server, ports 80 and 443 openGets a public certificate from Let's Encrypt or ZeroSSL automatically and renews itrestic.example.com { reverse_proxy localhost:8000 }
Home only, no public domainCreates its own local certificate with tls internalrestic.home.arpa { tls internal reverse_proxy localhost:8000 }

Publish the REST server only to the local machine, so Caddy is the only thing on the network: docker run -d --name rest-server -p 127.0.0.1:8000:8000 -v ~/docker_volumes/restic/backups:/data restic/rest-server:0.14.0. Then install Caddy with the official method for your system (caddy 2.11.7 is the latest release at the time of writing):

  • Linux: the official Debian and Ubuntu packages from caddyserver.com (sudo apt install caddy), then run Caddy with your Caddyfile.
  • Windows: the official static binary from caddyserver.com, then caddy run --config <path-to-your-Caddyfile>.
  • Mac: brew install caddy, then caddy run --config <path-to-your-Caddyfile>.

For a home-only certificate, run caddy trust on the Caddy machine. Other devices must trust Caddy's root certificate, found under Caddy's data folder at pki/authorities/local/root.crt. restic can use that file through the documented RESTIC_CACERT variable.

Check it worked. A public domain answers with a valid certificate:

Run in terminalbash
curl -I https://restic.example.com/

Expected output shape (the REST server answers with an HTTP status line; the exact status depends on its configuration):

Codetext
HTTP/2 200

For the home setup, use the same command with --cacert pointing at the root file, or check in a browser that the certificate is trusted.

Security and testing#

Protect three things: the repository password, the data in the repository, and any network door to the repository.

AreaWhat to doWhy
PasswordKeep it in a password manager and one second safe place. Never put it inline on the command lineLosing it means the data is irrecoverably lost; the process list can show it
Password fileMode 600 on Linux and Mac; only your user on Windows (icacls step 2)The restic FAQ asks for a restricted password file
RepositoryKeep it on a disk or NAS you controlThe data is encrypted, but sizes and timing stay visible to whoever holds the files
NetworkFor the REST server, use HTTPS and a user filePlain HTTP sends the user name and password in clear text (REST server docs)

What leaves the machine. A local repository on your disk or NAS sends nothing anywhere. A remote repository (S3, SFTP, the REST server) receives encrypted files only: restic encrypts them with AES-256 in counter mode and authenticates them (restic design document). What the remote side still sees is the number, sizes and timing of uploads and the names of the bucket or path you chose. The restic docs do not describe any telemetry, and this guide does not claim there is none.

Functionality test. The backup works when snapshots lists a new snapshot, check ends with no errors were found, and the step 7 restore brings back the files. Run these three after every change to the setup.

Secrets. The only secret here is the repository password. Do not commit ~/docker_volumes/restic/config to a Git repository or copy it into a chat. For S3, the AWS variables from the restic docs (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) are the documented way to pass the keys.

Tip: Run the restore test before you trust a new setup, and again after each restic upgrade.

Common mistakes#

Each row comes from the restic docs, from issues reported on the restic GitHub tracker, or from the way AI assistants often answer this question.

MistakeWhy it happensDo this instead
Losing the repository passwordNobody writes it down; the docs say it cannot be recoveredStore it in a password manager and one second safe place, then test a restore with the file you saved
Putting the password on the command line or in RESTIC_PASSWORD=... resticIt is quick to typeUse RESTIC_PASSWORD_FILE with a file that is mode 600, as in step 2 (restic FAQ)
Piping a dump into restic backup --stdinThe pipe hides a failed dump, so restic saves an empty backup (restic docs)Dump to a file, check the exit code, and back up the folder (step 4)
Backing up a running database's data folderThe files change while they are copied, so they may not matchStop the container or dump the database first
Running the container without --hostnameDocker gives a random host name each time, and restic uses the host name (restic Docker docs)Set a fixed host name such as --hostname docker-backup in every command
Upgrading to 0.19.1 straight awayThe release is new, and open reports say forget --prune fails on S3 and check --read-data reports false errorsStay on 0.18.1 until those issues are closed, then test on a throwaway repository
Expecting forget --prune to be safe without a previewPruning deletes data for goodRun forget --dry-run first, then the real command
Expecting an empty repository path to be an errorAn empty RESTIC_REPOSITORY file made restic use the current directory as the repository (issue #22051)Check the repository path with snapshots before a backup
AI assistants inventing a --password flagOther tools have one, so assistants copy itrestic has --password-file and --password-command; check the options page
AI assistants saying restic has a built-in schedulerMany backup tools dorestic has no scheduler; use cron, Task Scheduler or a systemd timer

Tip: Set --hostname in every command. Then the host name in snapshots matches for every run.

Tip: Run forget --dry-run before forget --prune. The dry run shows exactly which snapshots would go.

Tip: Dump the database first and check the dump's exit code. A backup of an empty dump still succeeds, and you only find out during a restore.

Troubleshooting#

ProblemSolution
Fatal: repository does not exist (exit code 10)You pointed RESTIC_REPOSITORY at a folder without a repository. Check the path, or run init once (step 3)
Wrong password (exit code 12)The password file does not match the repository. Check the file for typos, and that it has no extra characters
Failed to lock repository (exit code 11)Another restic process holds a lock, or a run was interrupted. Wait for the other run to finish, then run unlock (below)
backup exits with code 3Some files could not be read. A snapshot was still saved. Fix the permissions and run again
Prune fails on S3 with 0.19.xKnown issue #22092. Stay on 0.18.1
check --read-data reports errors on 0.19.xKnown issue #22105. Stay on 0.18.1; if errors persist on a current version, ask on the restic forum
Snapshots appear under a random hostThe container had no --hostname. Set one in every command

Known bugs (open in October 2026)#

  • #22092 (filed October 2026): forget --prune on S3 fails with Fatal: failed finding blobs: error decoding tree: expected [, got <nil>. The reporter says the same job worked on 0.18.1 and failed after 0.19.0. Workaround: pin 0.18.1.
  • #22105 (filed October 2026): check --read-data reports "wrong data returned" on 0.19.1, with errors on a local disk and none on a CIFS share. A commenter points at hardware; the reporter has ruled hardware out. Not confirmed by the project. Workaround: pin 0.18.1, and keep a second check on another machine if it matters.
  • #22085 and #22083 (filed September 2026): a restore with --overwrite if-changed can zero-fill a partly changed file that has hard links, and a restore can hang when fallocate never returns. Restore into a new, empty folder to avoid both.

Diagnostic commands:

Run in terminalbash
docker run --rm --hostname docker-backup -v ~/docker_volumes/restic/backups:/repo -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro -e RESTIC_REPOSITORY=/repo -e RESTIC_PASSWORD_FILE=/pass ghcr.io/restic/restic:0.18.1 unlock
docker logs notes --tail 100
docker inspect notes_data

Use unlock only when no other restic run is active. The restic troubleshooting page says a damaged repository should be checked, not patched by guesswork.

Updates and backups#

Update restic. Read the release notes first, test on a throwaway repository, then change the tag in every command. Never pull latest into a production backup.

Run in terminalbash
docker pull ghcr.io/restic/restic:<new-version>

Scheduled backup on Linux and Mac. Save this as ~/docker_volumes/restic/backup-notes.sh, make it executable, and run it from cron every night at 03:00:

File contentbash
#!/usr/bin/env bash
# backup-notes.sh - stop the app, back up its volume, start it again
IMG=ghcr.io/restic/restic:0.18.1
docker stop notes || exit 1
docker run --rm --hostname docker-backup \
  -v notes_data:/data:ro \
  -v ~/docker_volumes/restic/backups:/repo \
  -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro \
  -e RESTIC_REPOSITORY=/repo \
  -e RESTIC_PASSWORD_FILE=/pass \
  $IMG backup /data --tag notes
RC=$?
docker start notes
exit $RC
File contenttext
0 3 * * * ~/docker_volumes/restic/backup-notes.sh >> ~/docker_volumes/restic/logs/backup.log 2>&1

Scheduled backup on Windows. Save the same steps as backup-notes.ps1 and register it with Task Scheduler:

Codepowershell
$img = 'ghcr.io/restic/restic:0.18.1'
docker stop notes
docker run --rm --hostname docker-backup -v notes_data:/data:ro -v "$HOME\docker_volumes\restic\backups:/repo" -v "$HOME\docker_volumes\restic\config\restic-password.txt:/pass:ro" -e RESTIC_REPOSITORY=/repo -e RESTIC_PASSWORD_FILE=/pass $img backup /data --tag notes
$rc = $LASTEXITCODE
docker start notes
exit $rc
Run in terminalpowershell
schtasks /Create /SC DAILY /ST 03:00 /TN "restic notes backup" /TR "powershell -NoProfile -File $HOME\docker_volumes\restic\backup-notes.ps1"

Keep the right number of snapshots. The forget command removes old snapshots by rule. Preview it first:

Run in terminalbash
docker run --rm --hostname docker-backup -v ~/docker_volumes/restic/backups:/repo -v ~/docker_volumes/restic/config/restic-password.txt:/pass:ro -e RESTIC_REPOSITORY=/repo -e RESTIC_PASSWORD_FILE=/pass ghcr.io/restic/restic:0.18.1 forget --tag notes --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --dry-run

Remove --dry-run only after the preview looks right. The --keep-* options count periods that have at least one snapshot, as the restic docs describe.

Restore test. Repeat step 7 once a month, and keep the restored sample folder for comparison.

Performance and resources#

  • No official minimum RAM or CPU is published, and this guide measured none. Watch the usage with docker stats during a backup.
  • Memory: the tuning page says GOMAXPROCS=1 can slightly reduce memory use, at the cost of speed.
  • Tuning: RESTIC_PACK_SIZE and RESTIC_READ_CONCURRENCY exist; change them only after reading the tuning page.
Run in terminalbash
docker stats --no-stream

FAQ#

Does restic run on Windows and Mac? Yes. This guide uses Docker Desktop on both. The restic docs also list native installs (winget on Windows, brew on Mac).

What leaves my machine? Nothing, with a local repository. With a remote one, only encrypted files leave (see Security and testing).

Does restic schedule backups itself? No. It runs when executed, so you schedule it with cron, systemd or Task Scheduler.

Why not version 0.19.1? It is the latest release, but two open issues on the prune and check paths make it risky to pin for a backup you depend on. Move when they are closed and you have tested it on a throwaway repository.

Sources#

Checked against the official docs on 2026-10-30.