Skip to content
Sirio is in private beta: sources and installer are sent to testers by email. Ask to join.

Backups

Sirio backs up each site with restic: its Docker volumes, the host folders its containers mount, and its databases, dumped with their own tools. Snapshots are encrypted with a password only you hold, deduplicated (a nightly backup only stores what changed) and pruned according to a retention policy you choose.

restic runs as a one-off container, started by Sirio through the Docker socket: no privileged helper and no exception to the service’s sandbox are needed, and Sirio itself never reads the data. The image is pinned by digest (SIRIO_RESTIC_IMAGE overrides it).

The snapshots are plain restic snapshots. Everything Sirio does can be undone or inspected with restic alone, from any machine, even if the server and Sirio are gone: see Recovery kit.

A destination is a restic repository. Several plans, and several sites, can share one.

Type Repository Credentials
Local folder a path on this server none
SFTP sftp:user@host:/path an SSH key, generated by Sirio or pasted
Amazon S3 s3:s3.<region>.amazonaws.com/<bucket>/<folder> access key and secret key
S3-compatible s3:<endpoint>/<bucket>/<folder> access key and secret key
Backblaze B2 s3:https://s3.<region>.backblazeb2.com/<bucket>/<folder> key ID and application key
Google Cloud Storage gs:<bucket>:/<folder> a service account’s JSON key
  • Local folder. Docker creates it if it’s missing. On the same disk as the data it protects from mistakes, not from a disk failure: use a second disk, a mounted NAS, or another destination as well.
  • SFTP. Sirio connects with its own key and only to the host key you confirmed: when you create the destination, Sirio reads the server’s key and shows its fingerprint, which you compare with the server’s own (ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub). A server presenting another key later is refused, never trusted on first use: backups to it fail before anything is dumped or stopped, and the run says so. If the server was reinstalled, edit the destination and confirm its new fingerprint. If Sirio generates the key, add the public key it shows to ~/.ssh/authorized_keys of the user on the server. Avoid backup as the user name on Debian-based servers: it’s a system account there, whose home isn’t where you’d put the key.
  • S3-compatible. MinIO, Wasabi, Cloudflare R2, Hetzner, Scaleway, OVH and the like: give the endpoint as your provider documents it, and the region if it asks for one.
  • Backblaze B2 goes through B2’s S3-compatible API, which restic’s documentation recommends over the native b2 backend. The endpoint is in the bucket’s details (s3.<region>.backblazeb2.com).
  • Google Cloud Storage. The service account needs Storage Object Admin on the bucket. The project ID is taken from the key when left empty.

Give each destination credentials limited to its bucket. They’re stored encrypted with Sirio’s secret key, like Git tokens, and the API never returns them: editing a destination leaves a credential field empty to keep the stored one.

Every snapshot is encrypted with the repository’s password. When you create a destination Sirio generates one and shows it once: save it outside the server, in a password manager. Without it nobody can read the backups — Sirio can’t recover it for you, and if the server is lost, the copy inside Sirio’s database is lost with it.

To use a repository that already exists (made by hand, or by another Sirio), choose Use the password of an existing restic repository instead.

Check opens the repository with its password and creates it if it doesn’t exist yet — on S3-compatible storage, B2 and GCS the bucket too, if the key may create one. The first backup does the same on its own. restic retries a failing request for about fifteen minutes, which helps a backup ride out a network blip; checks, snapshot listings and the start of every run give up after three attempts instead and report restic’s error, so wrong credentials show up in seconds.

A plan backs up one site to one destination, on a schedule, keeping what the retention says.

The containers assigned to the site (in Containers, or claimed by a deploy of a pipeline linked to the site). For each one the plan decides how:

Mode What happens
Database dump the database is exported with its own tool while it runs. Consistent, and its data files are left out. The default for a recognized database.
Stop while copying the container is stopped while its files are copied, then started again. Consistent, with a short downtime.
Copy while running the files are copied while the container runs. The default for everything else: fine for uploads and static files, not for a database’s own files.
Leave out nothing of this container is backed up.

What’s copied: Docker volumes (named and anonymous) and bind-mounted host folders. Never: the Docker socket, the host’s system paths (/proc, /sys, /dev, /run, /etc/hosts and the like), tmpfs mounts. A volume shared by two containers is copied once.

Each volume and host folder can be left out on its own: untick it in the plan’s container table. The choice belongs to the plan, not to the container, since a volume shared by two containers is copied once — leaving it out from one leaves it out from both. A host folder left out that sits inside one still copied (say /srv/shop/media/thumbs under /srv/shop/media) is skipped by restic. A container in Stop while copying mode whose mounts are all left out isn’t stopped.

A database in dump mode that isn’t running can’t be dumped: its files are at rest, so they’re copied instead, which is safe.

Sirio recognizes the official images (and Bitnami’s) of MySQL, MariaDB, Percona and PostgreSQL (with PostGIS, TimescaleDB, pgvector), by image name: applications connecting to a database carry its variables too, so the environment alone would mislead.

The dump runs inside the database’s container (docker exec), with a fixed script that reads the credentials from the container’s own environment — the variables you set up the image with. No password passes through Sirio, and none ends up on a command line.

  • MySQL / MariaDB: as root when MYSQL_ROOT_PASSWORD / MARIADB_ROOT_PASSWORD (or their _FILE variants, or the empty-password ones) are set, everything with --all-databases; otherwise as MYSQL_USER on MYSQL_DATABASE only. mariadb-dump when present (MariaDB 11 no longer has mysqldump). The plan editor warns when no credentials are found.
  • PostgreSQL: pg_dumpall --clean --if-exists as POSTGRES_USER (default postgres), roles included: the dump drops each database before recreating it, so it restores over a live cluster.

The dump is written to a temporary folder under SIRIO_DATA_DIR/backups, included in the snapshot and deleted right after: it needs as much free space there as the dump’s size. A dump that fails leaves no file behind, so a truncated dump never looks like a good one; the run is marked failed, but the rest of the site is still backed up.

Every hour at a minute, every day or every week at a time, or a cron expression (five fields, plus @hourly, @daily, @weekly…), in the server’s time zone. A backup missed while Sirio was down runs once when it starts again. A plan can also be run only by hand.

A backup and a deploy of the same site never overlap: whichever starts second waits, and its log says so. Backups run one at a time.

A pipeline linked to a site can back it up as one of its steps: add a Backup step, pick one of the site’s plans, and put it before the deploy steps. The step waits for the snapshot (its timeout bounds the backup too) and fails the run if the backup fails, so a deploy never goes ahead without its safety net. The backup shows up in the plan’s history like any other, started by “pipeline”.

After each backup, restic’s forget --prune removes this plan’s snapshots beyond the counts you set — the last N, one per day for N days, one per week, one per month — and frees their space. With every count at zero nothing is ever removed.

Retention only touches the plan’s own snapshots (tag plan:<id>, taken from this host), so plans sharing a destination don’t prune each other.

Recovery kit (in Destinations, after confirming your own password) shows what’s needed to reach the backups with restic alone: the repository, its password, the variables naming the storage credentials (those stay yours to fill in), and the commands to list and restore.

Terminal window
export RESTIC_REPOSITORY='s3:https://s3.eu-central-003.backblazeb2.com/my-backups/sirio'
export RESTIC_PASSWORD='…'
export AWS_ACCESS_KEY_ID='…'
export AWS_SECRET_ACCESS_KEY='…'
restic snapshots --tag site:shop
restic restore latest --host web1 --tag site:shop --target ./restore

Inside a snapshot, backup/<site>/ holds volumes/<volume> (Docker volumes), binds/<host path> (host folders) and databases/<container>.sql (dumps). To restore a database, feed the dump to the database’s client: mysql < shop-db.sql or psql -f shop-db.sql.

Every snapshot carries the tags sirio, site:<site> and plan:<plan id>, and the server’s host name.

A plan’s page (its name in Backups) lists its snapshots — only its own, even when the destination is shared — and lets you walk any of them folder by folder. From there:

  • Download any file as it is, or any folder as a .zip, streamed out of the repository.
  • Restore a volume: over the site’s own volume, or into a new one (the current data stays untouched; mount the new volume where you need it). Replacing stops the containers using the volume, makes its content exactly the snapshot’s — files added since are deleted — and starts them again.
  • Restore a host folder, in place only, among the folders the site’s containers still mount: same stop, replace, start.
  • Restore a database from its dump into one of the site’s running database containers. By default the site’s other containers are stopped meanwhile, so no application writes halfway and PostgreSQL can drop the databases it recreates. MySQL/MariaDB stop at the first error; for PostgreSQL, the two errors about the role the restore runs as are expected, any other fails the restore. Dumps taken before Sirio added --clean restore cleanly only into an empty database.

Restores run in the same queue as backups, wait for any backup or deploy of the site, and have a page with their live log. Every target is checked twice, when asked and when the restore starts: a restore can only write into what belongs to the plan’s site, never elsewhere on the host.

  • Backups (global permission): destinations, and the plans of every site. Its exceptions apply everywhere, like the other global permissions.
  • On a site: Backups (seeing the site’s plans, runs and snapshots, and the names in them), Edit (creating, editing and deleting its plans, choosing among the existing destinations), Run (running and canceling a backup by hand) and Restore and download (reading the backed-up data back: downloading files, restoring them over the site’s). Restore is its own action because it can replace live data.

Someone with only site permissions sees the destinations’ names, not where they are.

  • Run logs: SIRIO_DATA_DIR/backups/logs, kept for the last SIRIO_RUN_RETENTION runs of each plan.
  • restic’s cache: the Docker volume sirio-restic-cache, so a run doesn’t download the repository’s indexes again.
  • While a run lasts: dumps under backups/staging, credential files under backups/tmp (0600, mounted read-only into restic’s container, deleted at the end).

A Sirio restart in the middle of a backup or a restore marks it failed, removes the restic container it left behind and starts again the containers it had stopped.