Using Restic with systemd on Linux
You can read the Russian version of this post here.
Restic, the simple backup program, is a fairly well-known piece of software. Designed to be simple to both use and script on any system, it doesn’t include any OS-specific setup examples, which is precisely what this post describes.
So what we’re trying to achieve here:
- Automated backup runs daily at 12:00 AM (or any configurable time).
- The backup includes only important configuration files and data stores.
- The backup also includes all PostgreSQL databases, restorable with
- The backup expands to an unlimited number of repos on need.
This guide assumes we’re backing up to a rest-server instance
192.168.1.200. The configuration should still be trivial to adapt
to pretty much any storage provider. This guide also assumes you have already
initialized a rest-server repo with
restic init -r rest:http://192.168.1.200/your-repo/.
For this we will need two systemd services, two correspondent timers, and a single helper script.
Backing up files/directories
We’d rather not want to run restic as root for obvious reasons, so let’s create a dedicated user:
# sudo useradd -m -N -s /usr/sbin/nologin restic
To backup files, we’ll need this service along with its timer:
[Unit] # this unit can be activated with a parameter, e.g. in # systemctl start email@example.com # %I is "your-repo" Description=Restic backup on %I After=syslog.target After=network-online.target [Service] Type=oneshot User=restic # runs restic backup on the files listed in /etc/restic/your-repo.files ExecStart=/usr/local/bin/restic backup --files-from /etc/restic/%I.files # source repo and password from /etc/restic/your-repo.env EnvironmentFile=/etc/restic/%I.env AmbientCapabilities=CAP_DAC_READ_SEARCH [Install] WantedBy=multi-user.target
[Unit] # the timer, enabled as firstname.lastname@example.org, will trigger # email@example.com Description=Run Restic at 12:00 AM [Timer] OnCalendar=*-*-* 0:00:00 [Install] WantedBy=timers.target
The repo name is passed through an environment file in
/etc/restic, read by
systemd (systemd does this as root, and
/etc/restic should really be only
readable as root, so set permissions accordingly):
We also have to supply restic with a file/directory list to back up:
/var/lib/docker /etc/postgresql /etc/restic ...
Backing up databases
This one is just a little less trivial.
Restic supports backing up data provided through stdin, so we can feed it with
the output of
pg_dumpall. The only limitation is that systemd runs whatever
you specify in
execve(3), and, to use output redirection,
we’ll need a separate bash script:
#!/usr/bin/env bash set -euo pipefail /usr/bin/sudo -u postgres /usr/bin/pg_dumpall --clean \ | gzip --rsyncable \ | /usr/local/bin/restic backup --host $1 --stdin \ --stdin-filename postgres-$1.sql.gz
postgres, we’ll need the following sudo rule in
restic ALL=(postgres) NOPASSWD:/usr/bin/pg_dumpall --clean
The unit file is as trivial as this:
[Unit] Description=Restic PostgreSQL backup on %I After=syslog.target After=network-online.target After=postgresql.service Requires=postgresql.service [Service] Type=oneshot User=restic ExecStart=/usr/local/bin/pgdump.sh %I EnvironmentFile=/etc/restic/%I.env [Install] WantedBy=multi-user.target
The timer isn’t really any different from the one we’ve already seen above:
[Unit] Description=Run Restic on PostgreSQL at 12:00 AM [Timer] OnCalendar=*-*-* 0:00:00 [Install] WantedBy=timers.target
Start the timers and enable their autostart at boot. Remember that
is used to expand file paths in
# systemctl enable --now firstname.lastname@example.org # systemctl enable --now email@example.com
Test whether the whole backup system is working:
# systemctl start firstname.lastname@example.org # systemctl start email@example.com
These unit files allow backing up to an unlimited number of repos as long as the
relevant configuration is provided through
Links / sources
Recipe to backing up PostgreSQL in a Docker container used for the backup script was originally found on restic forum. The excellent systemd docs (systemd.service, systemd.timer) also helped a lot.