Skip to content
Docs menu

For companies

Backup & restore

Offline, checksummed backups of every scope, and a restore that never leaves a half-restored server.

What a backup contains

  • identity.db: users, teams, memberships, token hashes and the admin event log.
  • One SQLite database per scope (every personal, team and the shared scope), including authorship, revision history and idempotency records.
  • learning.db: onboarding curricula, quizzes, progress and scores. New in 14.6.0
  • manifest.json with the package version, time and a SHA-256 checksum of every database.

Not included: model files (downloaded again), client token files (hand them out again or keep them in your password manager; their hashes are in the backup, so existing tokens keep working after a restore) and logs.

Take a backup

The server must be stopped: a running server or a leftover worker process blocks the backup, so it can never copy a database mid-write.

Python package:

# stop the server first (however you run it), then:
tam-team --root /srv/tam-team backup --out /backups/tam-team-2026-09-25

Docker Compose (the destination must be a separate mounted directory):

docker compose -f docker-compose.team.yml stop team-memory
docker compose -f docker-compose.team.yml run --rm -v /srv/tam-backups:/backups team-memory \
  python /app/src/team_memory/cli.py backup --out /backups/tam-team-2026-09-25
docker compose -f docker-compose.team.yml start team-memory

Rules the command enforces:

  • The destination must not exist yet and must be outside the data directory.
  • Every copied database is checked with SQLite’s integrity and foreign-key checks before the manifest is written.

Schedule it nightly during a quiet hour with cron or a systemd timer, and copy the result off the machine.

Restore

Restore always goes into a new, empty directory:

tam-team --root /srv/tam-team-restored restore --from /backups/tam-team-2026-09-25
  • The manifest, paths and every checksum are verified first, then each database is copied into a temporary staging directory and verified again.
  • Only when everything passes is the staging directory moved into place. A failure leaves no partially restored server.
  • A restored-from.json file records which backup and version it came from.

Then start the server with --root /srv/tam-team-restored.

Docker Compose. The profile keeps data at the root of the volume, and restore needs a directory that does not exist yet, so restore into a subdirectory of a new volume and then move the files up:

docker compose -f docker-compose.team.yml stop team-memory
docker volume create tam-team-restored
docker compose -f docker-compose.team.yml run --rm \
  -v tam-team-restored:/restore -v /srv/tam-backups:/backups team-memory \
  python /app/src/team_memory/cli.py --root /restore/data restore --from /backups/tam-team-2026-09-25
docker run --rm -v tam-team-restored:/v alpine sh -c 'mv /v/data/* /v/ && rmdir /v/data'
TAM_TEAM_DATA_VOLUME=tam-team-restored docker compose -f docker-compose.team.yml up -d

Put TAM_TEAM_DATA_VOLUME=tam-team-restored in .env so later commands use the restored volume. Keep the old volume until you have checked the restored server.

Test your backups

Restore into a scratch directory on another machine now and then, start a server on 127.0.0.1 against it, and search for a record you know exists. A backup you have never restored is a hope, not a backup.

Found a mistake? Open an issue on GitHub.

Search