Optimised Backups on NixOS with restic & fd<!-- --> — felschr's blog
Felix Schröter
HomeBlog
NixOS plus Restic

Optimised Backups on NixOS with restic & fd

I'd like to present a simple restic backup configuration I've been using that allows excluding a lot of unnecessary files via ignore patterns & by respecting .gitignore, thus shrinking backup sizes.

The Problem

Backups are important but not all files need to be backed up. Excluding paths from our backups can be very useful when we know the data is reproducible. E.g., if the target system runs podman containers that are all declared via NixOS options we don't really need most of the data in /var/lib/containers. When using volumes, though, /var/lib/containers/storage/volumes should be backed up. Other examples of files to exclude would be: caches, temporary files, artifacts in development directories. We might also decide that some data, while not reproducible, just isn't important to back up.

Restic

For my personal needs I've decided to use restic as my backup solution. I won't go into the details why I chose it over other solutions, but some of the reasons I went with restic are:

  • existing NixOS module
  • many storage options (e.g. Backblaze)
  • incremental backups
  • deduplication
  • strong encryption
  • zstd compression

In my experience restic is easy to use, fast & has some powerful options.

Finding the right approach

While the NixOS options for restic allow specifying paths to include via services.restic.backups.<name>.paths there is no native option to add exclusion patterns. So I've looked at the other options and found services.restic.backups.<name>.dynamicFilesFrom, which expects a script that produces a list of paths to back up.
This is great. My initial ideas was to generate a list of all files we want to back up, but this would become huge and even worse, restic's parent-snapshot detection will fail when paths used for --files-from change (#2246).

So, instead I need to do the opposite: have few backup paths and exlude a list of files to ignore. Looking further into restic's documentation, I found the option --exclude-file which does exactly that. We can use this option in NixOS via services.restic.backups.<name>.extraBackupArgs

Now that we found a way that allows us to exclude bloat from our backups, let's look how that might look in practise next.

Full solution

This is a simplified version of what I'm using in my desktop configuration. For a complete list check out my actual config. Hopefully some of these ignore patterns will fit your use case.

services.restic.backups.full = { initialize = true; repository = "SOME-REPO"; timeConfig.OnCalendar = "daily"; paths = [ "/etc/nixos" "/var/lib/" "/home" ]; extraBackupArgs = let ignorePatterns = [ "/var/lib/systemd" "/var/lib/containers" "/var/lib/flatpak" "/home/*/.local/share/Trash" "/home/*/.cache" "/home/*/Downloads" "/home/*/.npm" "/home/*/Games" "/home/*/.local/share/containers" "/home/felschr/dev" # backup ~/dev-backup instead ".cache" ".tmp" ".log" ".Trash" ]; ignoreFile = builtins.toFile "ignore" (foldl (a: b: a + "\n" + b) "" ignorePatterns); in [ "--exclude-file=${ignoreFile}" ]; pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 3" "--keep-yearly 1" ]; };
Respecting .gitignore

As you might have noticed above, I've excluded my ~/dev directory from backups. Since my development folder is huge and has lots of dependencies, build artifacts, etc., I've decided the best way to include it in my backups is to exclude everything that's mentioned in .gitignore files.

For this, I've decided to tackle this by generating ~/dev-backup in the pre-start script of restic's systemd service. In there I use rsync to clone ~/dev to ~/dev-backup. With the --filter option rsync supports exclude files like .gitignore natively. By also providing the --link-dest option, we tell rsync to create links to the original files instead of duplicating them. This way we don't have any storage overhead for this setup.

The resulting configuration looks like this:

systemd.services."restic-backups-full" = { preStart = '' rm -rf /home/felschr/dev-backup ${pkgs.rsync}/bin/rsync \ -a --delete \ --filter=':- .gitignore' \ --link-dest=/home/felschr/dev \ /home/felschr/dev/ /home/felschr/dev-backup ''; };

I hope you found this small guide useful and I could spare you some of the pitfalls I ran into before.

My full restic config can be found here: https://gitlab.com/felschr/nixos-config/tree/main/services/restic

UPDATE 2023-01-07

The article has been adjusted to fix an issue in the original approach. For more details of the changes check out the git history of this article.

Terms of Service