Backup and Restore¶
Backups are configured under Administration > Operations > Backups
(/v2/admin/operations): when to back up via the backup actions, where
to via the storage target.
The runner is built on restic (ADR 2026-08-11):
calServer produces the database dump and orchestrates schedule and retention;
restic carries storage, deduplication, encryption and integrity. A run backs
up the dump plus the application files (storage/app: DMS, file management,
inbox, reports).
The repository key IS the backup
Every run requires BACKUP_RESTIC_PASSWORD in the server environment
(host .env with Docker, a Secret on Kubernetes). Without the key no
backup runs — there is deliberately no unencrypted fallback. The key
intentionally does not live in the database: it would be lost together
with the data in the very case backups exist for.
Setting the key means filing it in the password manager. Losing the key means losing every backup.
Limits of the first iteration
Restore is CLI-guided (see below), no UI yet. SFTP targets need an SSH key — restic speaks SFTP through OpenSSH, password login cannot run unattended. MSSQL as the V2 engine has no dump path yet (a clear error instead of half a backup); PostgreSQL and MySQL/MariaDB are covered.
Backup actions: when to back up¶
| Field | Meaning |
|---|---|
| Description | Name of the backup |
| Start / End | Time window in which the backup may run |
| Weekdays / Months / Days of month | Repetition inside that window |
| Max. backups | How many backups are kept before the oldest is dropped |
| Active | Disabled entries do not run |
Clone creates a copy of a configuration, handy for a second, differing backup.
Storage target: where to back up¶
A backup inside the container is not a backup
The default is a local directory (storage/app/backups). That works without
any setup, but if the volume or the machine is lost, the backups go with it —
precisely when you need them. For regular operation the target belongs
outside.
| Storage type | Use for |
|---|---|
| Local directory | Getting started, or a volume mounted from outside |
| S3-compatible object storage | Amazon S3 and anything speaking its protocol: hoster object storage, MinIO, Ceph, Garage on premises |
| SFTP server | A backup target on your own network, via password or private key |
| Field | Meaning |
|---|---|
| Subfolder | Separates several calServer installations sharing one target. Optional. |
| Directory | Local storage only. Empty means the default. |
| Bucket / Region | S3: target bucket and region. Services without regions accept the default. |
| Endpoint | S3: only needed for services other than Amazon, as a full URL |
| Use path style | S3: needed for most self-hosted services without bucket subdomains |
| Host / Port / User | SFTP access |
| Secret key, Password, Private key | Credentials |
Credentials¶
Secrets are stored encrypted in the database (Laravel Crypt, i.e. against
the installation's APP_KEY) and never returned in clear text. The form only
shows whether something is stored; leaving a field untouched keeps the stored
value.
After changing the APP_KEY
A value encrypted with a different key counts as absent, and the form asks for re-entry. That is deliberate: otherwise the backup run would fail on an undecryptable string instead of someone re-entering the credentials.
Test the connection¶
Test connection writes a small probe file, reads it back and deletes it again. Reading back is part of it: a target you can write to but not read from accepts backups and never returns them — which would only surface during a restore, at the worst possible moment.
A failed test is information, not an application error: the target's message is shown below it.
No third-party service without a decision
S3 and SFTP have to be entered deliberately. Without those details no file leaves the server.
Triggering and checking runs¶
The schedule runs inside the Laravel scheduler (task "Backups", checked every five minutes, at most one run per action and window). By hand:
# Run all due actions (same thing the scheduler does):
docker exec calserver-api-v2 php artisan backup:run
# Run one specific action immediately, bypassing the due check:
docker exec calserver-api-v2 php artisan backup:run --action=1
The Test run button on an action starts the same run as a background
task; if a prerequisite is missing (key, storage target), the response names
it directly. Existing backups are listed by
GET /api/v2/admin/backup-snapshots — live from the repository, not from a
table of its own.
Restore¶
calServer V2 has no restore screen yet. The path goes through restic and the tools of the respective database:
# Inspect snapshots (repository address and key as in the container):
docker exec calserver-api-v2 sh -c 'restic snapshots' # RESTIC_* see below
# Restore one snapshot into a directory:
docker exec calserver-api-v2 restic restore <snapshot-id> --target /tmp/restore
RESTIC_REPOSITORY and RESTIC_PASSWORD correspond to the storage target
and BACKUP_RESTIC_PASSWORD; for S3 the target credentials come on top. The
restored directory contains the database dump (database.pgdump or
database.sql) next to the file directories; import it with the tools of the
respective database (pg_restore, mysql).
After importing with explicit IDs under PostgreSQL, realign the sequences, otherwise the next record collides with an assigned id: