Backing up Gogs in Docker with Restic

Published:
Last edit:

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:

  1. restic restore --target /

    This should create /tmp/gogs-backup.zip, and restore all the repositories into /var/gogs/git/gogs-repositories/2

  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.