CLI Backup Tools: Restic, Borg, Kopia

February 2, 2026

Time for some early spring cleaning.

I've got a shoddy backup system in place that needs to be revamped. Dropbox (which can't be fully downloaded onto my laptop, its so large) and five external hard drives, each a snapshot of the Dropbox drive at a different point over the last 10 years and with some other random things thrown onto them.

I used to have Backblaze but quit it years ago for some reason I don't remember.

I'd like to follow the 3-2-1 Backup Rule:

  • 3 copies of my data
  • 2 different media (e.g. cloud and external hard drive)
  • 1 copy off-site

I actually have all this covered but not in a consistent fashion – none of the 3 copies are the same or all up-to-date. My off-site drive is in Seattle and hasn't been updated for at least 3 years.

If I could fit my Dropbox onto my laptop then I would maybe do the hacky things and periodically create a whole copy to two empty external hard drives, recycling the drives for each update. But I can't anymore.

I want a tool that helps me make backups with deduplication, so I can add to an existing backup rather than make a whole new copy. I don't want to have to hop around between backups because my 2022 photos are in one backup and my 2023 photos are in another.

My Use Cases

I'm not doing anything too fancy nor are the stakes super high.

There are three things I want to be able to do easily:

  1. Maintain a backup of my data that I can browse occasionally.

  2. Backup my recent documents and photos on a monthly basis.

  3. Browse and tag my photos.

I have Dropbox and iPhoto both backing up my recent photos from my phone. And any recent documents on my laptop that could be lost between monthly backups is probably in my email or another cloud service, nor is it super important if I didn't already put it in Dropbox, we're basically talking my Downloads folder and the random screenshots I like to take of good or bad UX on the web that I never actually look at again.

Testing backup tools

I'm testing out three open source command line tools to see which one I like best.

I don't trust any company fully with my data and it sounds like the open source tools do a solid job, so why not go with free?

I had ChatGPT come up with a very basic testing script so I could get a feel for each tool. Basically, it's a directory with a couple files that I backup, make some changes, backup again, and restore from the original backup. Making some changes allows me to see how the tool handles deduping.

1mkdir -p ~/backup-test/{src1,src2,src3}
2cd ~/backup-test
3
4echo "file A" > src1/a.txt
5echo "file B" > src1/b.txt
6echo "file C" > src1/c.txt

Restic

Restic Documentation — restic 0.18.1 documentation
Language: Go

 1brew install restic
 2
 3export RESTIC_REPOSITORY=/backup-test #doesn't have to be the same dir
 4export RESTIC_PASSWORD=testpassword
 5
 6restic init
 7
 8restic backup src1
 9
10# only the changes will be backed up
11rm src1/b.txt
12echo "file D" > src1/d.txt
13
14restic backup src1
15
16repository 3de9effd opened (version 2, compression level auto)
17created new cache in /Users/boo/Library/Caches/restic
18no parent snapshot found, will read all files
19[0:00]          0 index files loaded
20
21Files:           3 new,     0 changed,     0 unmodified
22Dirs:            1 new,     0 changed,     0 unmodified
23Added to the repository: 1.805 KiB (1.113 KiB stored)
24
25processed 3 files, 21 B in 0:00
26snapshot c6277153 saved
27
28# copy the current backup to another dir and add a file to test deduping
29cp -r src1 src2
30echo "file E" > src2/e.txt
31
32restic backup src2
33
34repository 3de9effd opened (version 2, compression level auto)
35using parent snapshot c6277153
36[0:00] 100.00%  1 / 1 index files loaded
37
38Files:           1 new,     0 changed,     2 unmodified
39Dirs:            0 new,     1 changed,     0 unmodified
40Added to the repository: 1.791 KiB (970 B stored)
41
42processed 3 files, 21 B in 0:00
43snapshot 3d20c1b2 saved
44
45# list snapshots
46restic snapshots
47
48repository 3de9effd opened (version 2, compression level auto)
49ID        Time                 Host                        Tags   Paths                                Size
50---------------------------------------------------------------------------------------
51c6277153  2026-01-30 16:53:12  Nickis-Laptop.local              /Users/boo/dev/backup-test/src1  21 B
523d20c1b2  2026-01-30 16:59:52  Nickis-Laptop.local              /Users/boo/dev/backup-test/src1  21 B
533a9d7c2d  2026-01-30 17:00:05  Nickis-Laptop.local              /Users/boo/dev/backup-test/src2  28 B
54d55cba5e  2026-01-30 17:00:30  Nickis-Laptop.local              /Users/boo/dev/backup-test/src3  12 B
55---------------------------------------------------------------------------------------
564 snapshots
57
58# restore a snapshot
59restic restore  --target /tmp/restore-work {snapshot ID}
60
61repository 3de9effd opened (version 2, compression level auto)
62[0:00] 100.00%  4 / 4 index files loaded
63restoring snapshot c6277153 of [/Users/boo/dev/backup-test/src1] at 2026-01-30 16:53:12.927071 +0100 CET by boo@Nickis-Laptop.local to /tmp/restore-work
64Summary: Restored 4 files/dirs (21 B) in 0:00
65
66ls /tmp/restore-work
67
68rm -rf /tmp/restore-work

I like the restic commands the best of the three I've tried, mainly because I don't have to name the backup.

Borg

Language: Python

borg uses the term "archive" where restic uses "snapshot".

 1export BORG_REPO=/Users/boo/dev/backup-test-2/repo #needs to be an empty dir
 2export BORG_PASSPHRASE=testpassword
 3
 4borg init --encryption=repokey
 5
 6IMPORTANT: you will need both KEY AND PASSPHRASE to access this repo!
 7
 8Key storage location depends on the mode:
 9- repokey modes: key is stored in the repository directory.
10- keyfile modes: key is stored in the home directory of this user.
11
12For any mode, you should:
131. Export the borg key and store the result at a safe place:
14borg key export           REPOSITORY encrypted-key-backup
15borg key export --paper   REPOSITORY encrypted-key-backup.txt
16borg key export --qr-html REPOSITORY encrypted-key-backup.html
172. Write down the borg key passphrase and store it at safe place.
18
19borg create $BORG_REPO::path/to/src1 src1
20
21borg list $BORG_REPO
22src1     Sat, 2026-01-31 22:06:03 [e1bb98882f01e225e2a099aa83611fb91870ca5e5383d8391478c7b834e711ad]
23
24rm src1/c.txt
25echo "file D" > src1/d.txt
26
27borg create $BORG_REPO::src1-2 src1
28borg create $BORG_REPO::src2 src2
29
30borg list $BORG_REPO
31src1     Sat, 2026-01-31 22:06:03 [e1bb98882f01e225e2a099aa83611fb91870ca5e5383d8391478c7b834e711ad]
32src1-2   Sat, 2026-01-31 22:10:25 [32b95f572f19ed6d103a184789c6aa060324789fa3b87570de66356dcb0ce0c1]
33src2     Sat, 2026-01-31 22:12:42 [8a9fe564ab649e47736e1a01e81e116946bf74ca131e6e1b94b14b1ad2dea520]
34
35borg info
36Repository ID: be5ca57233423b0ad2cc12b2a262b20790d1dde7b9a4f0c34a93740f234c39e7
37Location: /Users/boo/dev/backup-test-2/repo
38Encrypted: Yes (repokey)
39Cache: /Users/boo/.cache/borg/be5ca57233423b0ad2cc12b2a262b20790d1dde7b9a4f0c34a93740f234c39e7
40Security dir: /Users/boo/.config/borg/security/be5ca57233423b0ad2cc12b2a262b20790d1dde7b9a4f0c34a93740f234c39e7
41------------------------------------------------------------------------------
42                        Original size      Compressed size    Deduplicated size
43All archives:                   42 B                300 B              2.87 kB
44
45                        Unique chunks         Total chunks
46Chunk index:                      10                   12
47
48borg extract $BORG_REPO::src1-2

The requirement to manually name each snapshot/archive could be either a pro or a con. The naming scheme I've used in the example above is terrible. I'm not sure what I'd want to use other than a date, which is repeated in the list.

As you can see from the init process, a key is created. You need both the key and the passphrase to access a borg repository.

Interesting features to explore:

  • compacting
  • compression

Kopia

Language: Go

 1kopia repository create filesystem --path /Users/boo/dev/backup-test-3/repo
 2Enter password to create new repository: testpassword
 3Re-enter password for verification: testpassword
 4Initializing repository with:
 5block hash:          BLAKE2B-256-128
 6encryption:          AES256-GCM-HMAC-SHA256
 7key derivation:      scrypt-65536-8-1
 8splitter:            DYNAMIC-4M-BUZHASH
 9Connected to repository.
10
11NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
12To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
13Alternatively you can remove the file "/Users/boo/Library/Application Support/kopia/repository.config.update-info.json".
14
15Retention:
16Annual snapshots:                 3   (defined for this target)
17Monthly snapshots:               24   (defined for this target)
18Weekly snapshots:                 4   (defined for this target)
19Daily snapshots:                  7   (defined for this target)
20Hourly snapshots:                48   (defined for this target)
21Latest snapshots:                10   (defined for this target)
22Ignore identical snapshots:   false   (defined for this target)
23Compression disabled.
24
25To find more information about default policy run 'kopia policy get'.
26To change the policy use 'kopia policy set' command.
27
28NOTE: Kopia will perform quick maintenance of the repository automatically every 1h0m0s
29and full maintenance every 24h0m0s when running as boo@nickis-Laptop.
30
31See https://kopia.io/docs/advanced/maintenance/ for more information.
32
33NOTE: To validate that your provider is compatible with Kopia, please run:
34
35$ kopia repository validate-provider
36
37kopia snapshot create /Users/boo/dev/backup-test-3/backup_dir
38Snapshotting boo@nickis-Laptop:/Users/boo/dev/backup-test-3/backup_dir ...
39* 0 hashing, 3 hashed (21 B), 0 cached (0 B), uploaded 211 B, estimating...
40Created snapshot with root k8631e4a74fa1bfb7327d82b503a46a8b and ID 1f6bf45929328b1372b1d8b37237e294 in 0s
41Running full maintenance...
42GC found 0 unused contents (0 B)
43GC found 0 unused contents that are too recent to delete (0 B)
44GC found 7 in-use contents (1.1 KB)
45GC found 2 in-use system-contents (1.2 KB)
46GC undeleted 0 contents (0 B)
47Compacting an eligible uncompacted epoch...
48Advancing epoch markers...
49Attempting to compact a range of epoch indexes ...
50Cleaning up unneeded epoch markers...
51Cleaning up old index blobs which have already been compacted...
52Cleaned up 0 logs.
53Finished full maintenance.
54
55kopia snapshot list /Users/boo/dev/backup-test-3/backup_dir
56boo@nickis-Laptop:/Users/boo/dev/backup-test-3/backup_dir
572026-02-01 10:33:07 CET k8631e4a74fa1bfb7327d82b503a46a8b 21 B drwxr-xr-x files:3 dirs:4 (latest-1..2,hourly-1,daily-1,weekly-1,monthly-1,annual-1)
58+ 1 identical snapshots until 2026-02-01 10:33:58 CET
59
60kopia ls -l k8631e4a74fa1bfb7327d82b503a46a8b
61drwxr-xr-x           21 2026-01-31 22:14:47 CET k6647c300e69cecccd21f920519c29abb  src1/
62drwxr-xr-x            0 2026-01-31 22:14:40 CET k1f7f41f99d34ce2bda1e9b82182cec5e  src2/
63drwxr-xr-x            0 2026-01-31 22:14:40 CET kf1038421ff24d338a963ef2deef7bc44  src3/
64
65kopia show k6647c300e69cecccd21f920519c29abb
66{"stream":"kopia:directory","entries":[{"name":"a.txt","type":"f","mode":"0644","size":7,"mtime":"2026-01-31T21:14:47.012334056Z","uid":501,"gid":20,"obj":"694bbedb98415f944e757fb04da85a00"},{"name":"b.txt","type":"f","mode":"0644","size":7,"mtime":"2026-01-31T21:14:47.012475682Z","uid":501,"gid":20,"obj":"680c1e63c73751bf535d6f56c1904958"},{"name":"c.txt","type":"f","mode":"0644","size":7,"mtime":"2026-01-31T21:14:47.012585432Z","uid":501,"gid":20,"obj":"cf894e34efbbdea5902193cd762aa89f"}],"summary":{"size":21,"files":3,"symlinks":0,"dirs":1,"maxTime":"2026-01-31T21:14:47.012585432Z","numFailed":0}}

I'd recommend creating env variables for kopia because it requires the use of absolute paths.

Interesting features to explore:

  • retention policies
  • automated backups
  • compression
  • ignore rules
  • GUI

After just a quick glance, kopia is exciting because it appears to have some great features for more complex use cases. There appears to be multiple contributors and an active user community. I like, for example, that there is a discourse forum (but unfortunately also a Slack). Complexity, on the other hand, often means more interdependencies and therefore higher risks.

Next Steps

I'm going to move forward with borg mainly because my partner also uses it (aka in-house help desk).

  • Set up two external hard drives with a full copy of Dropbox
  • Set up borg repos on the drives and my laptop
  • Create a script to run manually once a month
  • Test it out for a couple months, including my photo browsing & metadata update workflow

Note: Here's why syncing a backup repo isn't recommended: Can I copy or synchronize my repo to another location?