JP van Oosten

Running rsnapshot in a docker container on Synology NAS

May 4, 2020

I recently bought a NAS. This is new for me, because I've always built my own storage solutions using Linux servers. The last iteration was an Intel NUC with a few USB-disks attached. I was often anxious about the state of the disks, and replaced the big backup disks a few times. Last time, I decided it was time to upgrade. So, I ordered a Synology NAS. I've heard many good things about Synology and wanted a solution that would allow me to hot-swap faulty disks. The model I now have is a DS918+ with 4 disks.

In general, I like the idea of the NAS: a lot is just working out of the box and it's "just Linux" underneath. It has an easy-to-use interface and many packages available. However, the default backup strategy seems to be for your devices to push their stuff to the NAS. While this is fine for machines I log into regularly (like a desktop machine) and that tell you if a backup failed or not, for my servers I want my backup solution to pull the data instead. Mostly, because this will alert me when a backup fails. On my NUC, I used the fantastic rsnapshot utility, that uses a combination of rsync and hard links to build incremental backups. The Synology NAS does not have something for that though, so I set out to build it myself! This post documents what I did, in the hope that it helps others as well.

Prerequisites

The solution I ended up with depends on Docker, so I went to the Package Center and installed it. I also use the command line quite a bit, so I enabled SSH in Control Panel > Terminal & SNMP. In order to run docker as a user, I created a group "docker" using the Group Control Panel and added myself to it. Docker had to be restarted, which you can do from the Package Center interface1.

I created a Backups shared folder with an rsnapshot directory in there. This directory needs to have standard Linux permissions, instead of using the Synology's default ACL solution. chmod 750 rsnapshot from an SSH session did the trick. This is really important—I struggled with weird permission issues until I figured this out2.

The Dockerfile

I also created a docker/rsnapshots directory, that I use as the home for my Dockerfile and other files such as rsnapshot.conf and some helper scripts.

The Dockerfile is pretty easy:

FROM debian:stretch

RUN apt-get update -y && apt-get install -y rsnapshot

ADD --chown=root:root etc/rsnapshot.conf /etc/rsnapshot.conf
ADD --chown=root:root ssh/id_rsa /root/.ssh/id_rsa
ADD --chown=root:root ssh/config /root/.ssh/config

RUN ssh-keyscan -H HOST >> /root/.ssh/known_hosts
RUN chmod 0600 /root/.ssh/config /root/.ssh/id_rsa

I copied the rsnapshot.conf file from my previous backup-machine to a directory etc and add that to the docker image. This means that if you change your config, you need to rebuild your docker image. Not a big deal, but I might change this in the future if it annoys me. I also created an SSH key that I'll be using to connect to the servers with and some SSH config that specifies which user to use and the IdentityFile.

Note that I use ssh-keyscan to add the host key of my Linux server to the known_hosts file. If you use this approach, you need to add your own lines with the hosts you want to connect with.

To build the docker image, I used docker build -t rsnapshot:latest . from the directory that contains the Dockerfile.

Running rsnapshot

We are now ready to actually run rsnapshot! To make life a bit easier, I created a quick shell script run_rsnapshot.sh:

#!/bin/bash

mkdir -p /tmp/rsnapshot >/dev/null 2>&1

RSNAP=$(docker run -v /volume1/Backups/rsnapshot/:/backup/ -v /tmp/rsnapshot:/var/run -i rsnapshot /usr/bin/rsnapshot "$*" 2>&1)
EXITCODE=$?

if [ -z "$RSNAP" ]; then
    exit $EXITCODE
fi

echo "$RSNAP"

echo "Exited with code $EXITCODE"

exit 1

Basically, this script runs /usr/bin/rsnapshot in the docker container with the argument you give on the command line (usually something like hourly). In order to make sure that we don't have overlapping runs, we store the PID-file on the host. This is accomplished by creating a directory in /tmp and passing that along to the container (-v /tmp/rsnapshot:/var/run). The script also mounts the /volume1/Backups/rsnapshot directory at /backup in the container. Make sure you use /backup in your rsnapshot.conf!

Finally, it's good to know that the Synology task manager works a bit differently from cron. I noticed that sometimes rsnapshot does provide some output, but exits with a zero exit code. The Synology task-manager can either e-mail every time, or when the script exits with a non-zero exit code. I wanted to recreate the cron behaviour of e-mailing anytime there's output, so I wrap the script and check whether the output is empty or not. If it is empty, we can just exit, otherwise we present the output again and terminate the script with exit code 1.

rsnapshot now creates nice, incremental backups that uses hard links to ensure that only files that changed will take up extra disk space.

Conclusion

Using the Docker image and the power of rsnapshot, my Synology NAS now pulls in the backups from my Linux servers. I scheduled a few recurring tasks in the Control Panel and can be sure that my backups are being tended to.

In the end, I found out that even though the NAS uses Linux underneath, there's a lot different from a standard Debian install! The NAS uses non-standard tools like synoacltool, synoservicectl, etc.), it uses a different task scheduler, and the permissions are by default managed by the DSM software and ACLs. On the command-line I also miss a lot of tools like man and less. Luckily vim is installed :-)


  1. I later found out about synoservicectl, but it's quite hard to find out what the name of a service is... I think it should be possible to restart dockerd with synoservicectl --restart pkg-Docker-dockerd but haven't really tested this.

  2. It looks like the Synology NAS uses ACL to manage the permissions of files and directories. All files and directories have all Linux-permission bits set (i.e., mode 777), and docker didn't like that. In the container, it looked like all files and directories had no permission bits set (i.e., mode 000). Bypassing the ACL by changing the mode of the rsnapshot directory to 750 worked.