Skip to content

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:

docker exec calserver-api-v2 php artisan db:repair-sequences --dry-run
docker exec calserver-api-v2 php artisan db:repair-sequences