Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- [guide.md](docs/guide.md): full-backup section documents all four capture
|
||||||
|
methods (panel, `selfpost-backup`, stopped `tar` of the project directory,
|
||||||
|
stopped `tar` of `./data` only), warns against `tar` on a live container, and
|
||||||
|
compares downtime, archive contents, and restore behaviour in a table.
|
||||||
|
|
||||||
## [1.8.0] - 2026-08-18
|
## [1.8.0] - 2026-08-18
|
||||||
|
|
||||||
Built-in operator documentation in the panel: a CSS-only help drawer and Help
|
Built-in operator documentation in the panel: a CSS-only help drawer and Help
|
||||||
|
|||||||
+97
-26
@@ -31,6 +31,8 @@ domains hosted on that instance — DNS, deliveries, rate limits, applications).
|
|||||||
- [Server-level DNS (PTR/rDNS)](#server-level-dns-ptrrdns)
|
- [Server-level DNS (PTR/rDNS)](#server-level-dns-ptrrdns)
|
||||||
- [Rate limiting — level 1 (IP backstop)](#rate-limiting--level-1-ip-backstop)
|
- [Rate limiting — level 1 (IP backstop)](#rate-limiting--level-1-ip-backstop)
|
||||||
- [Full backup and restore](#full-backup-and-restore)
|
- [Full backup and restore](#full-backup-and-restore)
|
||||||
|
- [Taking a full backup](#taking-a-full-backup)
|
||||||
|
- [Restore](#restore)
|
||||||
- [Encrypting a backup or export](#encrypting-a-backup-or-export)
|
- [Encrypting a backup or export](#encrypting-a-backup-or-export)
|
||||||
- [Inbound relay](#inbound-relay)
|
- [Inbound relay](#inbound-relay)
|
||||||
- [DMARC reports](#dmarc-reports)
|
- [DMARC reports](#dmarc-reports)
|
||||||
@@ -459,26 +461,106 @@ logs under `data/log/` are excluded. The base compose file mounts the project
|
|||||||
directory read-only at `/selfpost-deploy` so the panel and CLI can read those
|
directory read-only at `/selfpost-deploy` so the panel and CLI can read those
|
||||||
deploy files — without that mount, *Full backup* refuses with an error.
|
deploy files — without that mount, *Full backup* refuses with an error.
|
||||||
|
|
||||||
Take a backup from the panel (*Backup* → *Full backup*) or from the host:
|
#### Taking a full backup
|
||||||
|
|
||||||
|
There are four ways to capture a full backup. They differ in whether the
|
||||||
|
instance keeps running, what goes into the archive, and what happens on restore.
|
||||||
|
|
||||||
|
**1. Panel — while the container is running (no downtime).** *Backup* → *Full
|
||||||
|
backup* (optionally tick *Encrypt with a password*). Same archive as the CLI
|
||||||
|
below.
|
||||||
|
|
||||||
|
**2. `selfpost-backup` CLI — while the container is running (no downtime).**
|
||||||
|
The panel button and this command call the same code path: a consistent SQLite
|
||||||
|
snapshot via `VACUUM INTO`, then a gzip tar of `data/` (minus diagnostics) plus
|
||||||
|
deploy files.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker exec <container> selfpost-backup > selfpost-backup.tar.gz
|
docker exec <container> selfpost-backup > selfpost-backup.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
**Restore** means unpacking that archive into an **empty project directory**
|
Use `-o /path/inside/container` to write inside the container instead of
|
||||||
(not into `./data` alone) and starting a container of the **exact same image
|
stdout. Optional encryption: set `SELFPOST_BACKUP_PASSWORD` or pass
|
||||||
version** that created it — SelfPost refuses to start otherwise and tells you
|
`-password-file` — see [Encrypting a backup or
|
||||||
which tag to use. On the first successful start after restore, `data/manifest.json`
|
export](#encrypting-a-backup-or-export).
|
||||||
from the archive is **deleted** — it guards only that one boot, so a later
|
|
||||||
in-place upgrade is not blocked. On that same first boot the panel also runs
|
**3. `tar` of the whole project directory — with the service stopped.** Stop
|
||||||
one **Resync** — OpenDKIM's tables and Postfix's sender map are re-derived from
|
the instance so nothing is writing to SQLite, then archive the operator
|
||||||
SQLite (and inbound relay maps when `INBOUND_RELAY_ENABLE=true`) and both
|
directory (the folder that contains `docker-compose.yml`, `.env`, `data/`, and
|
||||||
daemons are reloaded, healing any drift between the extracted files and the
|
usually `certs/`):
|
||||||
database (the Status page's *Reload configuration* button runs the same step on
|
|
||||||
demand). This is why the compose file pins a fixed tag rather
|
```sh
|
||||||
than `:latest`: without a known version, there'd be no way to tell which image
|
docker compose down
|
||||||
restoring a given backup actually requires (see [Fixed image
|
tar czf ../selfpost-backup.tar.gz .
|
||||||
tag](#fixed-image-tag)).
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Postfix delivery logs under `data/log/` are included and are often the bulk of
|
||||||
|
the archive; add `--exclude='./data/log'` if you only want state. Unlike the
|
||||||
|
panel/CLI backup, this archive has no `data/manifest.json`, so restore does not
|
||||||
|
run the version guard or the one-time post-restore *Resync* (see
|
||||||
|
[Restore](#restore) below) — a normal `docker compose up` is enough when the
|
||||||
|
files were captured cleanly while stopped.
|
||||||
|
|
||||||
|
**4. `tar` of `./data` only — with the service stopped.** Same as (3), but only
|
||||||
|
the data volume:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose down
|
||||||
|
tar czf ../selfpost-data.tar.gz ./data
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
This is **not** self-contained: you must keep `docker-compose.yml`, `.env`,
|
||||||
|
and `certs/` separately (another backup, or unchanged on the same host).
|
||||||
|
Restore is `tar xzf selfpost-data.tar.gz` into an existing project directory,
|
||||||
|
not into an empty one. Also includes `data/log/` unless you exclude it.
|
||||||
|
|
||||||
|
**Do not `tar` while the container is running.** SQLite uses WAL mode; copying
|
||||||
|
or archiving `./data` (or the whole project directory) while the panel and
|
||||||
|
Postfix are writing can produce an inconsistent database. The panel and CLI
|
||||||
|
backup avoid this by snapshotting the database on a live instance; the `tar`
|
||||||
|
methods avoid it by stopping first.
|
||||||
|
|
||||||
|
| | Panel / `selfpost-backup` | `tar` project dir (stopped) | `tar` `./data` only (stopped) |
|
||||||
|
|--|---------------------------|------------------------------|-------------------------------|
|
||||||
|
| Downtime | None | Yes (`docker compose down`) | Yes |
|
||||||
|
| Self-contained archive | Yes | Yes | No — deploy files separate |
|
||||||
|
| SQLite consistency | `VACUUM INTO` snapshot | Safe when stopped | Safe when stopped |
|
||||||
|
| `data/log/` | Excluded | Included (optional exclude) | Included (optional exclude) |
|
||||||
|
| `data/manifest.json` | Written (version stamp) | Absent | Absent |
|
||||||
|
| Restore version check | Yes — must match image tag | No | No |
|
||||||
|
| Post-restore *Resync* | Yes, on first boot | No | No |
|
||||||
|
| Password encryption (`.spbk`) | Yes | No — encrypt the `.tar.gz` yourself if needed | No |
|
||||||
|
|
||||||
|
Prefer the panel or CLI when you cannot afford downtime. Prefer a stopped `tar`
|
||||||
|
of the whole project directory when you already plan maintenance and want a
|
||||||
|
plain archive without going through the container. Use `tar` of `./data` only
|
||||||
|
when deploy files are managed elsewhere and never change.
|
||||||
|
|
||||||
|
#### Restore
|
||||||
|
|
||||||
|
**Panel and CLI archives** (with `data/manifest.json`) restore by unpacking into
|
||||||
|
an **empty project directory** (not into `./data` alone) and starting a
|
||||||
|
container of the **exact same image version** that created the backup — SelfPost
|
||||||
|
refuses to start otherwise and tells you which tag to use. On the first
|
||||||
|
successful start after restore, `data/manifest.json` from the archive is
|
||||||
|
**deleted** — it guards only that one boot, so a later in-place upgrade is not
|
||||||
|
blocked. On that same first boot the panel also runs one **Resync** — OpenDKIM's
|
||||||
|
tables and Postfix's sender map are re-derived from SQLite (and inbound relay
|
||||||
|
maps when `INBOUND_RELAY_ENABLE=true`) and both daemons are reloaded, healing
|
||||||
|
any drift between the extracted files and the database (the Status page's
|
||||||
|
*Reload configuration* button runs the same step on demand). This is why the
|
||||||
|
compose file pins a fixed tag rather than `:latest`: without a known version,
|
||||||
|
there'd be no way to tell which image restoring a given backup actually requires
|
||||||
|
(see [Fixed image tag](#fixed-image-tag)).
|
||||||
|
|
||||||
|
**Stopped-`tar` archives** (methods 3 and 4 above) have no `manifest.json`, so
|
||||||
|
there is no version guard and no automatic post-restore *Resync*. Unpack a
|
||||||
|
self-contained project archive into an empty directory (or replace `./data` in
|
||||||
|
place for a data-only archive) and `docker compose up -d`. Use an image tag
|
||||||
|
compatible with the data on disk; when in doubt, match the tag that was running
|
||||||
|
when the archive was taken.
|
||||||
|
|
||||||
**Restoring in place** (same host — recovering from data loss, or rolling
|
**Restoring in place** (same host — recovering from data loss, or rolling
|
||||||
back after a bad change):
|
back after a bad change):
|
||||||
@@ -548,17 +630,6 @@ on `/settings` deletes your other sessions, but a global administrator
|
|||||||
resetting another user's password on `/users` does not invalidate that user's
|
resetting another user's password on `/users` does not invalidate that user's
|
||||||
existing sessions.
|
existing sessions.
|
||||||
|
|
||||||
**Alternative: archive `./data` while stopped.** If the service can be taken
|
|
||||||
offline, `docker compose down` then `tar czf selfpost-data.tar.gz ./data` on
|
|
||||||
the host is safe — nothing is writing to SQLite. Unlike the panel/CLI backup
|
|
||||||
this sweeps in `./data/log/` too, which is Postfix's raw log and usually the
|
|
||||||
bulk of the archive; add `--exclude=./data/log` if you only want the state.
|
|
||||||
Do **not** tar `./data` while the container is running: the database uses
|
|
||||||
WAL mode and a naive copy can capture an inconsistent snapshot. The
|
|
||||||
panel/CLI backup remains preferable when you cannot afford downtime because
|
|
||||||
it takes a consistent SQLite snapshot via the Backup API on a live
|
|
||||||
container.
|
|
||||||
|
|
||||||
See also [Exporting and importing a single
|
See also [Exporting and importing a single
|
||||||
domain](#exporting-and-importing-a-single-domain) — a different, domain-scoped
|
domain](#exporting-and-importing-a-single-domain) — a different, domain-scoped
|
||||||
operation that also lives on the *Backup* page (`/backup`).
|
operation that also lives on the *Backup* page (`/backup`).
|
||||||
|
|||||||
Reference in New Issue
Block a user