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.0manifest.jsonwith 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.jsonfile 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.