Apps & Software

Kopia in Docker: Worth Switching From restic?

Kopia backup in Docker, judged against a working restic setup: what the policy system buys you, why compression is off, and the migration that does not exist.

Kopia backup in Docker — repository policies, the privileged compose file, compression defaults, and verifying a snapshot

⚡ The short version

Kopia's real difference from restic is where the rules live: the schedule, the retention and the compression setting sit in the repository rather than in your cron script, so a rebuilt server inherits them. That is worth something — probably not worth a migration, because there is no migration. No tool converts a restic repository, so moving means a fresh full backup and two storage bills while you build confidence. Compression is also off by default in Kopia and on by default in restic, which is the change most likely to surprise you.

This site has two posts about backing things up and both land on restic: the 3-2-1 strategy post calls it the recommended default, and the Docker volume post shows it archiving a named volume. So the open question about Kopia is not how to install it. It is whether someone with a working restic job should move.

Everything below comes from the two projects’ own repositories, read 5 October 2026 — Kopia at commit b1da599, restic at 5127c4a — plus the registry for what the images publish.

What Does Kopia Do That restic Doesn’t?

It remembers your decisions. That is the whole pitch, and it is narrower than the feature lists suggest.

With restic, a backup’s rules are arguments: which directories, how much compression, how many daily and weekly snapshots to keep all live in whatever script calls restic, and the repository knows none of it. With Kopia those are a policy, stored in the repository and attached to a path, a user, a host, or everything at once.

restic 0.19.1 Kopia 0.23.1
Where retention rules live flags on each forget run a policy in the repository
Snapshot scheduling your cron or systemd timer a policy field, acted on in server mode
Compression on by default, per run off by default, per policy
Housekeeping prune, when you run it automatic, roughly hourly and daily
Graphical interface none in the project KopiaUI, desktop and web
Multi-machine access control repository credentials, shared a repository server with per-user logins
Error-correcting codes not offered Reed-Solomon, opt-in at creation

Two of those rows need a caveat. The housekeeping is genuinely automatic — quick maintenance about every hour, full maintenance every 24 hours, with one user and host designated as maintenance owner so two clients never collide. It is not immediate, though: Kopia’s documentation says full maintenance may need several hours and multiple cycles to free unused blobs, because its safety rules depend on elapsed time. And the error correction carries a bigger asterisk — Kopia calls it experimental, it can only be switched on when a repository is created, and cloud storage mostly does its own anyway.

The repository server is the only row that solves a problem restic cannot: without it, every machine writing to shared storage holds credentials that can delete everyone’s data. Kopia’s server proxies that access and shows each user only their own snapshots — with one caveat it states itself, that only snapshot and policy manifests are access-controlled, not the content underneath.

How Do You Run Kopia in Docker?

With the project’s own image, which covers more architectures than most self-hosted software. Asking the registry what kopia/kopia:0.23.1 contains on 5 October 2026 returns linux/amd64, linux/arm64 and linux/arm/v7, so this runs on a 32-bit Raspberry Pi as well as a 64-bit one.

The image tag drops the v. The git tag is v0.23.1 and the registry 404s on it; the image is kopia/kopia:0.23.1. Floating latest, testing and unstable tags exist too, and unstable tracks nightly builds — not what you want holding your only backup.

The container needs more mounts than a typical app, because Kopia keeps three kinds of state apart: config in /app/config, where it looks for repository.config; cache in /app/cache, which if left unmounted means re-downloading metadata every run; logs in /app/logs. Then the data you are backing up, and the repository itself if it is a local path.

A minimal server-mode stack, following the shape of the project’s own example:

services:
  kopia:
    image: kopia/kopia:0.23.1
    hostname: backup-host
    restart: unless-stopped
    ports:
      - "51515:51515"
    command:
      - server
      - start
      - --address=0.0.0.0:51515
      - --server-username=admin
      - --server-password=${KOPIA_SERVER_PASSWORD}
      - --tls-cert-file=/app/config/kopia.cert
      - --tls-key-file=/app/config/kopia.key
    environment:
      KOPIA_PASSWORD: ${KOPIA_REPOSITORY_PASSWORD}
    volumes:
      - ./config:/app/config
      - ./cache:/app/cache
      - ./logs:/app/logs
      - /srv/appdata:/data:ro
      - /mnt/backup:/repository

Without one of those two lines the container restart-loops. The server exits with TLS not configured. To start server without encryption pass --insecure, and restart: unless-stopped turns that into a silent loop you only find in the logs.

Generate the pair once, before the first up:

docker compose run --rm kopia server start \
  --tls-generate-cert \
  --tls-cert-file=/app/config/kopia.cert \
  --tls-key-file=/app/config/kopia.key \
  --address=0.0.0.0:51515 --server-username=admin --server-password="$KOPIA_SERVER_PASSWORD"

Interrupt it once it prints SERVER CERT SHA256: for the self-signed certificate it just wrote. Then leave --tls-generate-cert out of the compose command — on the second start it finds its own file and exits with TLS cert file already exists.

The hostname line is not decoration. Kopia identifies a snapshot source by user and host, Docker hands out a random hostname per container, and a container recreated without a fixed one becomes a different source — separate policy, separate snapshot history. The documentation recommends setting it explicitly for that reason.

Mounting the data read-only is the instinct that applies to any Docker stack you are composing: a backup agent has no business writing to what it reads.

Why Is the Project’s Own Compose File Privileged?

Because of one feature, and you can probably turn it off.

The sample compose file in the Kopia repository asks for a lot: privileged: true, the SYS_ADMIN capability, apparmor:unconfined, /dev/fuse passed through, user: "0:0", and the host root mounted at /data read-only. That reads like an agent that needs to see everything. It mostly isn’t.

Those flags exist to make kopia mount work inside a container, not to make backups work. Mounting a snapshot as a browsable filesystem needs FUSE, and FUSE inside Docker needs the device and the capability. A container that only creates snapshots and restores files into a directory needs none of it. Granting privileged mode to a container that holds credentials for every copy of your data is a trade worth making deliberately — see hardening a self-hosted server for why that blast radius matters.

One more item in the published examples is a convenience rather than a setting: the installation snippet reaches the web interface over plain HTTP with --insecure and --disable-csrf-token-checks. The certificate pair above is the alternative to the first of those.

Does the Policy Schedule Actually Fire?

Only in server mode. This is the single most useful thing to know before you move, and no guide leads with it.

Kopia’s scheduling flags are real: kopia policy set takes --snapshot-interval, --snapshot-time for times of day, and --snapshot-time-crontab for cron expressions. The question is what acts on them. Searching the source on 5 October 2026, the code that tracks when a source is next due and starts the upload sits entirely inside the server component, and nothing outside it refers to that code.

So there are two coherent setups, and one that isn’t:

✅ Server mode owns the clock

  • The container runs server start and stays up
  • Schedules in policies fire on their own
  • You get KopiaUI's web interface with it
  • Retention and scheduling both survive a rebuild

❌ Cron owns the clock

  • A host cron job runs the container per snapshot
  • Scheduling fields in the policy do nothing
  • Retention and compression still come from the policy
  • Simpler, and identical in shape to a restic job

The third is setting a snapshot interval in a policy, running the container on demand, and assuming the backup is scheduled. Nothing errors. You simply have no backup until you next run the command by hand.

If you pick cron, be honest that you kept the part of restic you were trying to leave — though retention and compression still live in the repository rather than the script.

Compression: Off in Kopia, On in restic

This is the default most likely to cost you money quietly.

Restic has compressed by default since repository format version 2, which needs restic 0.14.0 or newer and is the current default for a new repository. Its setting is auto unless you say otherwise, and it is a per-run choice rather than a stored one.

Kopia is the opposite in both halves. Its FAQ states that compression is controlled by policies and disabled by default — so if compression is why you are switching, you have to ask for it:

kopia policy set --global --compression=zstd

The choices include zstd, pgzip, s2, gzip and deflate, each with speed and compression variants, and Kopia ships kopia benchmark compression to test them against a real file of your own. Its documentation recommends zstd if you would rather not think about it, and notes that s2, deflate and gzip use the least memory on small files — which matters on a Pi.

Neither tool compresses retroactively by default. Kopia says plainly that a new compression setting applies going forward and it will not recompress what is already uploaded. Restic will rewrite old data, but only if asked: upgrading a format version 1 repository leaves existing file contents alone until a prune run with the repack-uncompressed option.

Can You Migrate a restic Repository to Kopia?

No. There is no import, no conversion, and no partial path.

Grep Kopia’s whole documentation tree for restic and you get nothing — the word does not appear. The formats are unrelated and neither project offers a bridge, so switching is not a migration. It is standing up a second backup system from zero:

  1. Create the Kopia repository on separate storage, so a mistake cannot touch the restic one.
  2. Set the policies first, retention and compression both, because neither applies to what is already uploaded.
  3. Take the first full snapshot, and expect it to cost what your original restic seed cost.
  4. Keep both running for a few cycles.
  5. Restore something real out of Kopia, then delete the restic repository.

Step five is the one to do rather than intend. Until a file has come back out of the new repository, you have swapped a proven backup for an unproven one and a bigger bill.

Kopia's compatibility promise is the reassuring part. Its documentation guarantees each release reads snapshots written by the current and at least one previous minor version, refuses to write to a repository format it cannot safely handle, and never upgrades a repository format without explicit human action. That last clause is the one that matters for a thing you will not look at for two years.

How Do You Prove a Kopia Snapshot Restores?

With kopia snapshot verify, and you have to pass it a percentage or it checks less than you think.

Run bare, it confirms every index entry is backed by an existing blob — a real check, and one Kopia runs automatically during daily full maintenance. But its own documentation states the limit: it stops short of downloading, decrypting and decompressing anything, so it cannot see a blob that rotted after upload. For that, ask for files:

kopia snapshot verify --verify-files-percent=100 --file-parallelism=10

Downloaded files are discarded as it goes, so this costs bandwidth rather than disk, and the project suggests one percent daily for a large repository — roughly a 98% chance of covering everything over a year, by its own arithmetic. The option is command line only; KopiaUI cannot run it.

None of which is a restore. The Docker volume post makes that distinction, and it holds here unchanged.

So Should You Switch?

Stay on restic if your cron job works and lives in version control. You would pay a full re-seed and a second storage bill to move rules out of a file you already manage, and lose compression on day one without noticing.

Switch if one of three things is true: several machines back up to shared storage and you do not fully trust all of them, which is the repository server’s whole purpose and something restic has no answer to; your retention rules exist only in a crontab on a box you cannot rebuild; or somebody other than you needs to restore a file, where a web interface beats a documented command.

Run both for a month if you are unsure. It is the only version of this decision that cannot lose data.

Frequently Asked Questions About Kopia

Is Kopia better than restic?

Not better, differently shaped. Kopia stores the schedule, the retention rules and the compression setting inside the repository, so a rebuilt machine inherits them; restic reads all three from the flags your cron script passes it. If your script is version-controlled and working, Kopia buys you very little. If the rules live only in a crontab on a server you are about to replace, that is the real argument for moving.

Does Kopia compress backups by default?

No, and this catches people moving from restic, which does. Kopia’s own documentation says compression is controlled by policies and is disabled by default, so a repository created and left alone stores everything uncompressed. Restic enables compression automatically on repository format version 2, the default since restic 0.14.0. Turning it on in Kopia is one policy command, and it only applies to data uploaded afterwards.

Is there a tool to import restic snapshots into Kopia?

No. The two use incompatible repository formats and Kopia’s documentation does not mention restic anywhere, so there is no import tool and no conversion. Switching means creating a fresh Kopia repository, taking a full first snapshot, and paying for that storage twice until you are confident enough to delete the restic one. Keep the old repository readable until you have restored something real out of the new one.

Do you need to run Kopia in server mode?

Only if you want the schedule in the policy to actually fire. The scheduling code lives in Kopia’s server component, so a container that just runs snapshot commands never acts on a policy schedule. Either run the container in server mode and let it keep time, or keep driving it from cron and treat the scheduling fields as documentation. Both work; mixing them silently gives you nothing.

Why does Kopia’s own Docker Compose file need privileged mode?

Because of the mount feature, not the backups. The sample compose file in the project repository asks for privileged mode, the SYS_ADMIN capability, an unconfined AppArmor profile and access to the FUSE device, all so the container can mount a snapshot as a browsable filesystem. If you only need to create snapshots and restore files to a directory, you can drop every one of those and keep the config and cache volumes.

How do you check a Kopia backup is not corrupt?

Use the snapshot verify command, and give it a files percentage, because without one it only confirms that the blobs exist. Asking it for a percentage makes Kopia download, decrypt and decompress that share of your data, which is what actually catches bit rot. The project suggests verifying one percent daily, which it calculates reaches roughly all your data over a year, and this option is command line only.

Product links on this site are plain links. We earn nothing from them — see our disclosure policy.