Backing up Gogs in Docker with Restic
tl;dr
This should be added on the host, possibly as root
# crontab -l
SHELL=/bin/bash
PATH=/usr/bin:/bin:/snap/bin
0 0 * * * { docker exec gogs bash -c 'rm -f /backup/gogs-backup-*.zip && cd /app/gogs && mkdir -p /backup && chown -R git:git /backup ./log && gosu "git" ./gogs backup --target=/backup --exclude-repos' && docker cp gogs:$(docker exec gogs bash -c 'readlink -f /backup/$(ls /backup | grep gogs-backup-* | tail -n 1)') /tmp/gogs-backup.zip && restic backup -p /etc/gogsbkpwd --repository-file /etc/gogsbkrepo /tmp/gogs-backup.zip /var/gogs/git/gogs-repositories/ && restic forget -p /etc/gogsbkpwd --repository-file /etc/gogsbkrepo -l 15 -d 15 -w 14 -m 24 -y 6; rm -f /tmp/gogs-backup.zip && docker exec gogs bash -c 'rm /backup/gogs-backup-*.zip'; } 2>&1 >> /tmp/backup.log
# cat /etc/gogsbkpwd
<repo encryption password>
# cat /etc/gogsbkrepo
rest:https://<login>:<password>@restic.example.com
Walking through that blob of text
Environment setup
SHELL=/bin/bash
PATH=/usr/bin:/bin:/snap/bin
By default, crontab runs sh, and it’s $PATH is /usr/bin:/bin
. This doesn’t work for me, because Restic is installed from snap, and, as far as I know, command groups need bash
Creating database backup
docker exec gogs bash -c 'rm -f /backup/gogs-backup-*.zip && cd /app/gogs && mkdir -p /backup && chown -R git:git /backup ./log && gosu "git" ./gogs backup --target=/backup --exclude-repos'
This is mostly taken from this GitHub discussion.
Removes all old backups, and creates a new database and configuration backup. You could keep the old ones, but they’re kinda pointless without repositories
UPDATE 2023/05/01: This part was running as root before, so it would lead to permission issues with the log files. It now switches to the git user
Copying backup to the host
docker cp gogs:$(docker exec gogs bash -c 'readlink -f /backup/$(ls /backup | grep gogs-backup-* | tail -n 1)') /tmp/gogs-backup.zip
Copies last backup file from the docker to /tmp/gogs-backup.zip
on the host
Actually backing up
Now we’re getting to the sauce
restic backup -p /etc/gogsbkpwd --repository-file /etc/gogsbkrepo /tmp/gogs-backup.zip /var/gogs/git/gogs-repositories/
This uploads the repositoryless backup and git repositories from /var/gogs/
, which should be mounted on the host if you followed this installation
This isn’t exactly ideal, since it doesn’t lock git repositories. As far as i can see, this shouldn’t be an issue, since Gogs does exactly the same thing. 1 2
You could also switch out parameters for environment variables, but I didn’t bother.
Clean up
restic forget -p /etc/gogsbkpwd --repository-file /etc/gogsbkrepo -l 15 -d 15 -w 14 -m 24 -y 6
Removes old backups. This is optional, but I’ve decided to only keep a handful of backups.
For reference:
-l, --keep-last n keep the last n snapshots
-H, --keep-hourly n keep the last n hourly snapshots
-d, --keep-daily n keep the last n daily snapshots
-w, --keep-weekly n keep the last n weekly snapshots
-m, --keep-monthly n keep the last n monthly snapshots
-y, --keep-yearly n keep the last n yearly snapshots
rm -f /tmp/gogs-backup.zip && docker exec gogs bash -c 'rm /backup/gogs-backup-*.zip';
Removes the mess that we have created.
Logging
{ <commands>; } 2>&1 >> /tmp/backup.log
This groups all the command outputs, and redirects all of it to /tmp/backup.log
Restoration
Fortunately I haven’t had a need in backup restoration yet, but the process should be straight forward:
restic restore --target /
This should create
/tmp/gogs-backup.zip
, and restore all the repositories into/var/gogs/git/gogs-repositories/
2./gogs restore --exclude-repos --from="/tmp/gogs-backup.zip"
Load the database and whatnot into the Gogs instance.
Closing thoughts
Using the uncompressed repository reduces the size of each backup from the size of all repositories (~130 MiB in my case), to the size of Gogs database and configuration (~2 MiB). While this can be improved even more by decompressing the backup .zip, I have decided that it’s not worth complicating backup and restoration process over a few megabytes.
This script might lead to minor discrepancies in the backup between the database and git repository, and even backed up git repository corruptions, if we push at the same time as backing up. This is because we’re backing them up in two different steps. Though this shouldn’t be an issue, since, at the time of writing, Gogs does the same thing. 1
This script can be adapted for Gitea 3, Borg, and other git repository/incremental backup solutions. Doing that is an exercise to the reader.