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
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.
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.
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 statsAdditionally, 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 pruneThe commands exit with the status of restic (0 = success), so they can be used in your own scripts.
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.
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.
The container is set up by setting environment variables and volumes.
| 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> |
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. Withrotate:10every 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, raiseN– the check then reads less per week and takes longer to cover everything – or fall back to a fixed amount like1G. Versions before 1.8.1 read a random 1 GB per check; if you upgrade with a large repository and have not setRESTIC_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
- 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.
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_CRONto a weekly schedule and leave--pruneout ofRESTIC_FORGET_ARGS. Optional prune parameters go toRESTIC_PRUNE_ARGS. - With every backup: add
--prunetoRESTIC_FORGET_ARGSand leavePRUNE_CRONempty.
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.
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,forgetandcheckeach 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.
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).
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_backupSee the restic documentation for details and limits.
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 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: brevoGet 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}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 /restoreIf 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 /restoreAll commands from the official restic documentation are supported.
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/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 imageSometimes 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.
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
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.
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"
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