Skip to content

Repository files navigation

Restic Backup Docker Container

This Docker container automates restic backups and checks at regular intervals. It offers:

  • Simple setup and maintenance
  • Define backup, integrity check and prune schedules
  • Integrity check with rotating data verification and a weekly restore test
  • Hanging restic runs are killed and reported, they never block the following backups
  • Supports all restic targets (tested with: Local filesystem, AWS, Backblaze, and rest-server)
  • Custom hook integration
  • Partial and full restore
  • Includes mysql/mariadb dump before the backup run
  • Healthcheck (via https://healthchecks.io/) or email notifications

SeaTable and Seafile Specific Extension

This container is essentially a wrapper for the well-established backup software restic, suitable for any use case.

There is one SeaTable-specific extensions: a script dumps the SeaTable big data before the backup starts. This action is deactivated by default and must be enabled with this environment variable:

  • SEATABLE_BIGDATA_DUMP=true

This Docker container is part of the seatable docker release github repo, but it's essentially a restic backup container capable of backup up everything plus a mysql/mariadb dump.

How to use

Everything below /data/ in the container is part of the backup. Mount all data to be backed up as a read-only volume below /data/. All restic targets are supported, including rest-server, S3, Backblaze, and even the host filesystem. The backup and check schedule is executed via cron inside the container.

Important: Avoid mounting something directly to /data/ directory as read-only. Otherwise database dumps will not work because it creates a folder /data/database-dumps. Instead mount everything in subdirectory like /data/seatable-compose, /data/seafile or /data/anything-else.

Commands

You can easily execute any restic command in the Docker container. Refer to the official restic documentation] for more details. Here are some examples:

docker exec -it restic-backup restic [command]

# Examples:
docker exec -it restic-backup restic -h
docker exec -it restic-backup restic snapshots
docker exec -it restic-backup restic version
docker exec -it restic-backup restic stats

Additionally, there are three simple commands for backup, checking consistency and pruning. These commands are also executed according to the CRON schedule.

To manually perform a backup, check or prune, independent of the CRON, run:

docker exec -it restic-backup backup
docker exec -it restic-backup check
docker exec -it restic-backup prune

The commands exit with the status of restic (0 = success), so they can be used in your own scripts.

Hooks

The Container supports the execution of the following custom hooks (if available at the container). Hooks are skipped if no scripts are found.

  • /hooks/pre-backup.sh
  • /hooks/post-backup.sh
  • /hooks/pre-check.sh
  • /hooks/post-check.sh
  • /hooks/pre-prune.sh
  • /hooks/post-prune.sh

The post hooks receive the exit code of restic as first argument: 0 success, 3 backup successful but some files could not be read, 124/137 aborted by RESTIC_TIMEOUT, anything else failed. post-check.sh receives 1 if the restore test failed.

Logs

By default the container returns logs to stdout. You can get the log output of the container with docker logs restic-backup -f.

If LOG_TYPE is set to file, the container also writes a log file to /var/log/restic/backup.log which is mounted as volume to /opt/restic/logs in the host.

Customize the Container

The container is set up by setting environment variables and volumes.

Environment variables

Name Description Example Default
RESTIC_REPOSITORY Restic backup target /local or rest:https://backup.seatable.io required
RESTIC_PASSWORD Encryption password topsecret required
BACKUP_CRON Execution schedule for the backup 20 2 * * * (Do not enclose the schedule in quotes) 20 2 * * *
CHECK_CRON Execution schedule integrity check 40 3 * * 6 (Do not enclose the schedule in quotes) 40 3 * * 6
PRUNE_CRON Execution schedule for prune (see below) 40 3 * * 0 (empty = no separate prune job)
LOG_LEVEL Define log level DEBUG, INFO, WARNING or ERROR. INFO
LOG_TYPE Define the log output type stdout or file stdout
TZ Timezone Europe/Berlin
RESTIC_TAG Tag for backup seatable seatable
RESTIC_DATA_SUBSET Data read by the integrity check (see below) rotate:10, 1G, 10% or none rotate:10
RESTORE_TEST Restore a random file after each check true or false false
RESTIC_FORGET_ARGS Restic Forget parameters --keep-daily 6 --keep-monthly 6
RESTIC_JOB_ARGS Restic Job execution parameters --exclude=/data/logs --exclude-if-present .exclude_from_backup
RESTIC_PRUNE_ARGS Restic Prune parameters --max-unused 10%
RESTIC_SKIP_INIT Skip restic initialization true or false false
RESTIC_TIMEOUT Max. runtime per restic command (see below) 48h, 90m or 0 to disable 48h
SEATABLE_DATABASE_DUMP Enable mysql/mariadb database dump (DEPRECATED) true or false false
DATABASE_DUMP Enable mysql/mariadb database dump true or false false
DATABASE_HOST Name of the mariadb/mysql container mariadb mariadb
DATABASE_USER User for connection to database root root
DATABASE_PASSWORD Password for connection to database topsecret
DATABASE_LIST List of databases to export (empyt=all) dtable_db,ccnet_db,seafile_db
DATABASE_DUMP_COMPRESSION Compress database dump with gzip true or false false
SEATABLE_BIGDATA_DUMP Enable dump of big data true or false false
SEATABLE_BIGDATA_HOST Name of the SeaTable Server container seatable-server seatable-server
HEALTHCHECK_URL healthcheck.io server check url https://healthcheck.io/ping/a444061a
MSMTP_ARGS SMTP settings for mail notification --host=x --port=587 ... cdb@seatable.io
AWS_DEFAULT_REGION Required only for S3 backend eu-west-1
AWS_ACCESS_KEY_ID Required only for S3 backend
AWS_SECRET_ACCESS_KEY Required only for S3 backend
B2_ACCOUNT_ID Required only for backblaze backend
B2_ACCOUNT_KEY Required only for backblaze backend
USER_AGENT Define the user agent used with curl restic-backup-docker/<version> restic-backup-docker/<version>

Integrity check and restore test

CHECK_CRON (default: weekly) runs restic check, which verifies the structure of the repository: index, snapshots and that every referenced data block exists. What it reads of the actual data is controlled by RESTIC_DATA_SUBSET. restic stores every block under its SHA-256 hash and verifies the hash when reading, so a block that is read and passes is proven to be intact.

RESTIC_DATA_SUBSET Data read per check Coverage
rotate:N (default rotate:10) a fixed 1/N part, a different one every calendar week complete – every block is verified once every N weeks
1G or 10% a random 1 GB / 10 % of the repository random – a block may not be read for a long time
none nothing, structure only

⚠️ The default downloads 1/10 of your repository every week. With rotate:10 every weekly check reads a tenth of all data stored in the repository from the backend: 20 GB per week for a 200 GB repository, 200 GB per week for 2 TB. Make sure this fits your bandwidth and the traffic pricing of your storage provider (Backblaze B2 offers free egress up to three times the stored volume per month, AWS S3 and others charge per GB). If it does not, raise N – the check then reads less per week and takes longer to cover everything – or fall back to a fixed amount like 1G. Versions before 1.8.1 read a random 1 GB per check; if you upgrade with a large repository and have not set RESTIC_DATA_SUBSET, the weekly download grows accordingly.

Choose N so that repository size / N fits your weekly budget: rotate:4 for a 5 GB repository (1.3 GB per week, all data every month), rotate:10 for 200 GB (20 GB per week, all data every 10 weeks), rotate:52 for 2 TB (40 GB per week, all data every year). The part is derived from the ISO week number, so it only rotates completely with a weekly CHECK_CRON. The container logs which part is read: Check reads part 7/10 of the repository data.

Restore test: with RESTORE_TEST=true, every check additionally restores one random file (up to 100 MiB) of the latest snapshot with restic restore to a temporary directory inside the container and compares its SHA-256 with the original below /data, if that has not changed since the snapshot. This proves that the restore path works end to end. The result is logged (Restore test: restored /data/... from snapshot ..., checksum matches the original) and is part of the healthcheck body and the mail. A failed restore test is reported like a failed check: healthcheck /fail, exit code

  1. The restore test is skipped if the check itself failed.

To disable: RESTORE_TEST=false (default) turns off the restore test, RESTIC_DATA_SUBSET=1G limits the weekly download to a fixed amount, RESTIC_DATA_SUBSET=none turns off data reading, CHECK_CRON= (empty) turns off the scheduled check altogether.

Neither check proves that the backup contains everything you need to bring your application back. Test that periodically by hand: restore the latest snapshot to /restore, load the database dump into a fresh database and start the application with the restored data. And keep RESTIC_PASSWORD in a place that survives the loss of this server – without it, the repository is worthless.

Retention and prune

restic forget removes old snapshots according to RESTIC_FORGET_ARGS after every backup. The data of the removed snapshots is only deleted from the repository by restic prune. Prune is the most expensive restic operation: it downloads, rewrites and uploads pack files and makes many API requests. There are two ways to run it:

  • Separate prune job (recommended): set PRUNE_CRON to a weekly schedule and leave --prune out of RESTIC_FORGET_ARGS. Optional prune parameters go to RESTIC_PRUNE_ARGS.
  • With every backup: add --prune to RESTIC_FORGET_ARGS and leave PRUNE_CRON empty.

Without one of the two, forgotten snapshots are never removed from the repository and it grows forever. The container logs a warning on start if RESTIC_FORGET_ARGS is set without --prune and PRUNE_CRON is empty.

Backup, check and prune never run at the same time. If a job is scheduled while another one is still running, it is skipped and an error is logged. Choose schedules that do not overlap.

Timeout for hanging restic commands

RESTIC_TIMEOUT is a safety net against restic commands that hang forever. This can happen if a backend stops responding in a way that restic's own retry logic does not catch (observed with the native Backblaze b2: backend, see below). Without a timeout, such a run blocks all following scheduled backups silently, because the lock is never released.

  • The timeout applies per restic command: backup, forget and check each get the full time.
  • A valid backup is never affected as long as the timeout is larger than your longest run. Choose at least twice your longest expected runtime. For a first backup of a very large dataset over a slow line, raise it or disable it temporarily with 0.
  • If the timeout hits, restic is stopped, the repository lock is removed, the error is reported via healthcheck, mail and container log (aborted by RESTIC_TIMEOUT) and the next scheduled run works again.

Backblaze B2: use the S3 endpoint

The native b2: backend of restic uses a library that retries a failed upload endlessly, below restic's own retry limit. If a B2 storage pod becomes unreachable, restic backup or restic forget --prune hangs forever. We recommend to use the S3 compatible endpoint of Backblaze instead. It is the same bucket and the same repository, only the address changes:

RESTIC_REPOSITORY=s3:s3.<region>.backblazeb2.com/<bucket>/<path>
AWS_ACCESS_KEY_ID=<keyID>
AWS_SECRET_ACCESS_KEY=<applicationKey>

The region is part of the S3 endpoint shown in the bucket details of your Backblaze account (e.g. eu-central-003).

Large repositories: pack size

restic stores data in pack files of 16 MiB by default. For repositories with hundreds of GB on S3 or Backblaze B2 this means a very large number of files and API requests (which Backblaze bills per transaction). A larger pack size reduces both. Add it to RESTIC_JOB_ARGS, it only affects newly written packs:

RESTIC_JOB_ARGS=--pack-size 64 --exclude-if-present .exclude_from_backup

See the restic documentation for details and limits.

Healthcheck

If HEALTHCHECK_URL is set (e.g. a check at healthchecks.io), every job reports its progress: /start when it begins, /0 when backup and forget (or check, or prune) finished successfully, /fail otherwise. The body of /0 and /fail contains the last 300 lines of lastrun.log. Configure the grace time of the check to be longer than your longest run: a job that sends /start but no result within the grace time is reported as down, even if the container itself does not respond anymore. An unreachable healthcheck server never aborts a job.

Mail notification

Mail notification is optional. If specified, the content of /var/log/restic/lastrun.log is sent via mail after each backup, data integrity check and prune using an external SMTP. A failed run is reported as well. To have maximum flexibility, you have to provide a msmtp configuration file with the mail/smtp parameters on your own. Have a look at the msmtp manpage for further information.

Here is an example of MSMTP_ARGS, to specify the recipient of the notification.

# example of MSMTP_ARGS
MSMTP_ARGS="recipient@example.com"
MSMTP_ARGS="-a default recipient@example.com"

Here is the example of /opt/restic/msmtprc.conf to configure your external SMTP account.

defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        /var/log/restic/msmtp.log

account        brevo
host           smtp-relay.brevo.com
port           587
from           noreply@seatable.io
user           your-username
password       your-password

account default: brevo

Example docker-compose

Get the latest version of the container from https://hub.docker.com/repository/docker/seatable/restic-backup.

---
services:
  restic-backup:
    image: ${SEATABLE_RESTIC_BACKUP_IMAGE:-seatable/restic-backup:latest}
    container_name: restic-backup
    hostname: ${RESTIC_HOSTNAME:-restic-backup}
    restart: unless-stopped
    init: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/seatable-compose:/data/seatable-compose:ro
      - /opt/seatable-server/seatable:/data/seatable-server:ro
      - /opt/restic/local:/local
      - /opt/restic/restore:/restore
      - /opt/restic/cache:/root/.cache/restic
      #- /opt/restic/hooks:/hooks:ro
      #- /opt/restic/logs:/var/log/restic
      #- /opt/restic/msmtprc.conf:/root/.msmtprc:ro
    environment:
      - RESTIC_REPOSITORY=${RESTIC_REPOSITORY:?Variable is not set or empty}
      - RESTIC_PASSWORD=${RESTIC_PASSWORD:?Variable is not set or empty}
      # - RESTIC_TAG=${SEATABLE_SERVER_HOSTNAME:-seatable}
      # - BACKUP_CRON=${BACKUP_CRON:-15 2 * * *} # Start backup always at 2:15 am.
      # - CHECK_CRON=${CHECK_CRON:-45 3 * * 6} # Start check every saturday at 3:45am
      # - PRUNE_CRON=${PRUNE_CRON:-45 3 * * 0} # Start prune every sunday at 3:45am
      # - LOG_LEVEL=${LOG_LEVEL:-INFO}
      # - LOG_TYPE=${LOG_TYPE:-stdout}
      # - TZ=${TIME_ZONE}
      # - RESTIC_DATA_SUBSET=${RESTIC_DATA_SUBSET:-rotate:10} # Downloads and verifies 1/10 of the repository per week. Raise N for large repositories!
      # - RESTORE_TEST=${RESTORE_TEST:-true} # Restore a random file after each check and compare it with the original
      # - RESTIC_FORGET_ARGS=${RESTIC_FORGET_ARGS:- --keep-daily 6 --keep-weekly 4 --keep-monthly 6}
      # - RESTIC_PRUNE_ARGS=${RESTIC_PRUNE_ARGS:-}
      # - RESTIC_JOB_ARGS=${RESTIC_JOB_ARGS:- --exclude=/data/seatable-server/seatable/logs --exclude=/data/seatable-server/seatable/db-data --exclude-if-present .exclude_from_backup}
      # - RESTIC_SKIP_INIT=${RESTIC_SKIP_INIT}
      # - RESTIC_TIMEOUT=${RESTIC_TIMEOUT:-48h} # Max. runtime per restic command, 0 disables
      # - DATABASE_DUMP=${DATABASE_DUMP:-true}
      # - DATABASE_HOST=${DATABASE_HOST:-mariadb}
      # - DATABASE_USER=${DATABASE_USER:-root}
      # - DATABASE_PASSWORD=${SEATABLE_MYSQL_ROOT_PASSWORD:?Variable is not set or empty}
      # - DATABASE_LIST=${DATABASE_LIST}
      # - DATABASE_DUMP_COMPRESSION=${DATABASE_DUMP_COMPRESSION:-false}
      # - SEATABLE_BIGDATA_DUMP=${SEATABLE_BIGDATA_DUMP:-true}
      # - SEATABLE_BIGDATA_HOST=${SEATABLE_BIGDATA_HOST:-seatable-server}
      # - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-}
      # - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
      # - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
      # - B2_ACCOUNT_ID=${B2_ACCOUNT_ID:-}
      # - B2_ACCOUNT_KEY=${B2_ACCOUNT_KEY:-}
      # - HEALTHCHECK_URL=${HEALTHCHECK_URL}
      # - MSMTP_ARGS=${MSMTP_ARGS}

How to restore

Restore to host server

Restore files inside the Docker container to the /restore folder, which is mounted to the host system.

To restore files from the latest snapshot, use the following command:

docker exec -it restic-backup restic restore latest --target /restore

If you want to restore only a subset from an older snapshot, use this command:

# get snapshot ids
docker exec -it restic-backup restic snapshots

# get list of files in a snapshot (you will need the path to the files for partial restore)
docker exec -it restic-backup restic ls <snapshot>

# restore only config files from a specific snapshot
docker exec -it restic-backup restic restore <snapshot> --include /data/seatable-server/seatable/conf/ --target /restore

All commands from the official restic documentation are supported.

Mount

restic mount allows you to mount a snapshot to make it accessable like a local filesystem.

However, using "FUSE" (Filesystem in Userspace) in a Docker setup can create various problems. Therefore, we've removed everything related to mounting from this container. Please avoid using it.

Tests

tests/run.sh builds the image and runs a test suite against it: success paths with the real restic and a local repository, failure paths (hanging restic, timeouts, failing hooks, unreachable healthcheck server, lock conflicts) with a fake restic and a mock healthcheck server. It runs in GitHub Actions on every push and can be run locally, requirements are docker, python3 and curl:

tests/run.sh                                  # build and test
IMAGE=seatable/restic-backup:1.7.0 tests/run.sh   # test an existing image

Two backup targets

Sometimes you might want to backup to two different repositories. In this case it is no problem to run to backup containers in parallel. The volume configuration can be the same, but it is advised that you run the backups to different times. Therefore choose different values for BACKUP_CRON, CHECK_CRON and PRUNE_CRON.

Using rclone

Restic supports a variety of backup targets via rclone. While rclone is pre-installed in the container, you need to mount your rclone.conf file to the correct path for proper functionality. To mount your rclone.conf file, add the following to your custom-restic.yml:

services:
  restic-backup:
    # ... other configurations ...
    volumes:
      # ... other volumes ...
      - ./rclone.conf:/root/.config/rclone/rclone.conf:ro

Creating an rclone.conf file

The simplest way to generate your rclone.conf file is by using the rclone configuration management command line. Follow these steps:

docker run -it --entrypoint=/bin/bash seatable/restic-backup:latest -i
rclone config
# Follow the prompts to create your rclone configuration.
# Once completed, display the contents of the configuration file:
cat /root/.config/rclone/rclone.conf
# copy the content of this configuration file to the host and mount it to the container.

For more detailed information on configuring rclone, refer to the official Restic documentation.

Use rclone as target

The general backend specification format is rclone:<remote>:<path>, the <remote>:<path> component will be directly passed to rclone.

Your .env might look like this:

RESTIC_REPOSITORY="rclone:dropbox-restic:/Backup Folder"

Use SSH as protocol

If you want to backup via ssh, please use RESTIC_REPOSITRY like this sftp:<username>@<target-ip-or-url>:<path>

Restic does not support sending passwords directly for SFTP connections. Instead, it relies on passwordless authentication using SSH keys. This is because automated backups require a secure and non-interactive way to connect to the server, which passwordless SSH provides. Therefore create a new SSH key pair on your host and mount it to your container like this:

services:
  restic-backup:
    ...
    volumes:
      ...
      - /root/.ssh:/root/.ssh

About

No description, website, or topics provided.

Resources

Stars

6 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages