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.

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.
| What | Value |
|---|---|
| Latest release | restic 0.19.1, released July 2026 (release page) |
| Version used here | 0.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 needed | About 30 to 45 minutes for the first run (our estimate) |
| RAM and size | Not documented by the project; not measured by us |
| Ports | None. restic does not listen on a network port. The REST server option uses port 8000 |
| Licence | BSD 2-Clause (code in the LICENSE file) |
| Official docs | restic.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.
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.
| System | What you need | Notes |
|---|---|---|
| Linux | Docker Engine, a bash shell | The docker commands run as your user (add yourself to the docker group, or use sudo) |
| Windows | Docker Desktop with Linux containers, PowerShell | Use PowerShell 7 if you can. The folders live under $HOME\docker_volumes |
| Mac | Docker Desktop for Mac, Terminal | Same commands as Linux |
Check Docker first:
docker --version
docker --version
Checklist before you start:
- [ ] Docker runs and
docker --versionprints a version - [ ] You know the name of the volume to back up (
docker volume lslists 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#
docker pull ghcr.io/restic/restic:0.18.1 docker run --rm ghcr.io/restic/restic:0.18.1 version
Windows (PowerShell)#
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:
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):
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#
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)#
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:
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:
ls -l ~/docker_volumes/restic/config/restic-password.txt
Expected output shape (owner and size will differ; the permissions must be -rw-------):
-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#
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)#
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:
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):
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
USERline). On Linux, files it writes into a bind mount are owned by root. Plan to usesudoto 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:
- Simple apps: stop the container for the few minutes the backup takes, then start it again.
- 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#
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):
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)#
docker stop notes # ... run step 5, then: docker start notes
For a database:
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:
docker stop notes # ... run step 5, then: docker start notes
For a database:
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:
docker ps --filter name=notes
Expected output shape (the docs of Docker show this format; your columns and IDs will differ):
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#
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)#
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:
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:
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#
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)#
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:
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-datadownloads 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#
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)#
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:
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:
docker network create proxy
Save this as ~/docker_volumes/restic/docker-compose.yml (no version: line is needed), replacing <your-domain>:
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 case | What Caddy does | Caddyfile |
|---|---|---|
| Public domain, DNS A record to your server, ports 80 and 443 open | Gets a public certificate from Let's Encrypt or ZeroSSL automatically and renews it | restic.example.com { reverse_proxy localhost:8000 } |
| Home only, no public domain | Creates its own local certificate with tls internal | restic.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, thencaddy 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:
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):
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.
| Area | What to do | Why |
|---|---|---|
| Password | Keep it in a password manager and one second safe place. Never put it inline on the command line | Losing it means the data is irrecoverably lost; the process list can show it |
| Password file | Mode 600 on Linux and Mac; only your user on Windows (icacls step 2) | The restic FAQ asks for a restricted password file |
| Repository | Keep it on a disk or NAS you control | The data is encrypted, but sizes and timing stay visible to whoever holds the files |
| Network | For the REST server, use HTTPS and a user file | Plain 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.
| Mistake | Why it happens | Do this instead |
|---|---|---|
| Losing the repository password | Nobody writes it down; the docs say it cannot be recovered | Store 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=... restic | It is quick to type | Use RESTIC_PASSWORD_FILE with a file that is mode 600, as in step 2 (restic FAQ) |
Piping a dump into restic backup --stdin | The 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 folder | The files change while they are copied, so they may not match | Stop the container or dump the database first |
Running the container without --hostname | Docker 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 away | The release is new, and open reports say forget --prune fails on S3 and check --read-data reports false errors | Stay on 0.18.1 until those issues are closed, then test on a throwaway repository |
Expecting forget --prune to be safe without a preview | Pruning deletes data for good | Run forget --dry-run first, then the real command |
| Expecting an empty repository path to be an error | An 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 flag | Other tools have one, so assistants copy it | restic has --password-file and --password-command; check the options page |
| AI assistants saying restic has a built-in scheduler | Many backup tools do | restic has no scheduler; use cron, Task Scheduler or a systemd timer |
Tip: Set
--hostnamein every command. Then the host name insnapshotsmatches for every run.
Tip: Run
forget --dry-runbeforeforget --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#
| Problem | Solution |
|---|---|
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 3 | Some files could not be read. A snapshot was still saved. Fix the permissions and run again |
| Prune fails on S3 with 0.19.x | Known issue #22092. Stay on 0.18.1 |
check --read-data reports errors on 0.19.x | Known issue #22105. Stay on 0.18.1; if errors persist on a current version, ask on the restic forum |
| Snapshots appear under a random host | The container had no --hostname. Set one in every command |
Known bugs (open in October 2026)#
- #22092 (filed October 2026):
forget --pruneon S3 fails withFatal: 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-datareports "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-changedcan zero-fill a partly changed file that has hard links, and a restore can hang whenfallocatenever returns. Restore into a new, empty folder to avoid both.
Diagnostic commands:
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.
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:
#!/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
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:
$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
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:
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 statsduring a backup. - Memory: the tuning page says
GOMAXPROCS=1can slightly reduce memory use, at the cost of speed. - Tuning:
RESTIC_PACK_SIZEandRESTIC_READ_CONCURRENCYexist; change them only after reading the tuning page.
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#
- restic README and licence (v0.19.1) - read October 2026
- restic LICENSE (BSD 2-Clause) - read October 2026
- restic documentation: installation, preparing a new repository, backup, working with repositories, tuning parameters, restore, forget, encryption, scripting and exit codes, troubleshooting, FAQ - read October 2026
- restic design document (encryption) - read October 2026
- restic Docker image notes (v0.18.1) and release Dockerfile - read October 2026
- restic release workflow for the Docker image - read October 2026
- restic releases: 0.19.1 (July 2026) and 0.18.1 (September 2025) - read October 2026
- restic GitHub issues #22092, #22105, #22085, #22083, #22051, #22006 - read October 2026
- REST server README and Docker notes (v0.14.0, May 2025) - read October 2026
- Docker Hub: restic/rest-server - read October 2026
- Caddy: install, automatic HTTPS, command line, tls directive, Docker Hub: caddy - read October 2026
- Traefik: Docker provider - read October 2026
Checked against the official docs on 2026-10-30.