Backups
Back up and restore the database, the encryption key and the bucket.
Back up and restore the database, the encryption key and the bucket.
What to Back Up
- Database: PostgreSQL database, including the job queue tables (
job_queue,job_schedules). It includes the bucket settings with their encrypted secret - Files: S3 bucket contents
- Configuration:
.envfile - Encryption key: See Encryption Key
Encryption Key
Every stored file and the saved bucket secret are encrypted under this key. Without it, a restored database and bucket cannot be decrypted, and the key cannot be recovered.
Where the key is:
| Setup | Variable | Location |
|---|---|---|
Docker (setup.sh) | FILE_ENCRYPTION_KEY_FILE | tma-cloud/secrets/file_encryption_key |
| Key file outside Docker | FILE_ENCRYPTION_KEY_FILE | The path set in .env |
Key in .env | FILE_ENCRYPTION_KEY | The value in .env |
After a rotation the key file holds every key version. Back up the whole file: a database backup can only be read with the versions that were in use when it was taken. See Key Rotation.
How to keep it:
- Copy the key once, after setup, and again after each key rotation. It does not change otherwise.
- Store it apart from the database backups, for example in a password manager. A backup that holds both the database and the key gives full access to the files.
- To restore, put the key back at the same location with the same value before starting the containers. The server refuses to start with a different key.
Before Updates
update.sh dumps the database to backups/pre-update-<time>.dump before each update and keeps the last 3. These dumps are for rolling back an update, not a replacement for regular backups. See Updating.
Backup Script
TMA Cloud includes a backup and restore script, db-backup-restore.sh. setup.sh puts it in the install directory and update.sh keeps it current. In a repository checkout it is scripts/db-backup-restore.sh. It handles full PostgreSQL backups and restores through Docker or the PostgreSQL client tools on the host.
Run it from the install directory with ./db-backup-restore.sh; the examples below use that form.
Without a local copy, run it in the install directory:
curl -fsSL https://raw.githubusercontent.com/TMA-Cloud/TMA/main/scripts/db-backup-restore.sh | bash -s -- backupThe words after -- are the command and its arguments, for example bash -s -- restore backups/<file>.dump. The script uses the current directory as the install directory; set TMA_DIR to use another one. A restore asks for the database name on the terminal, because the script itself arrives on standard input. Without a terminal, the restore is cancelled.
How It Works
- Uses
pg_dumpwith custom format (compressed binary, supports selective and parallel restore) - Takes a
--serializable-deferrablesnapshot (consistent read without blocking writes) - Verifies the dump with
pg_restore --listimmediately after creation. The dump is written as<name>.dump.partand renamed only after this check, so a failed dump leaves no file behind - Computes a SHA-256 checksum and writes a
.metasidecar file - Records per-table row counts at backup time
- Auto-prunes its own old backups based on
BACKUP_RETAIN_COUNT(default 10). Thepre-update-*.dumpfiles fromupdate.share not counted or removed - Writes dumps with mode
0600in a0700backups/directory
Container Detection
The script finds the PostgreSQL container in this order:
DB_CONTAINER(explicit override)- The
postgresservice of the Docker Compose project in the script's directory
Otherwise it uses the PostgreSQL client tools on the host and connects to the host and port in DATABASE_URL. It never picks a container by name or image, because a restore replaces the database.
Database Backup
Create a Backup
./db-backup-restore.sh backupOutput is saved to backups/<database>_<TIMESTAMP>.dump, named after the database in DATABASE_URL, with a .meta file alongside it.
Example Output
── Full Database Backup ──
[INFO] Connected to 'tma_cloud_storage' via Docker container 'tma-cloud-postgres'
[INFO] Database : tma_cloud_storage
[INFO] Collecting table statistics …
[INFO] Running pg_dump (custom format, serializable snapshot) …
[INFO] Verifying backup integrity (pg_restore --list) …
[OK] Backup contains 145 TOC entries.
[INFO] Computing SHA-256 checksum …
── Backup Complete ──
[OK] File : backups/tma_cloud_storage_20260115T020000Z.dump
[OK] Size : 12M
[OK] Duration : 4s
[OK] TOC entries: 145
[OK] SHA-256 : a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2Metadata File
Each backup has a .meta file with:
timestamp=20260115T020000Z
database=tma_cloud_storage
user=postgres
mode=docker
format=custom
compression=--compress=6
toc_entries=145
file_size=12M
duration_seconds=4
sha256=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
# Table row counts at backup time
public.users=5
public.files=218
public.audit_log=1340
public.job_queue=64Database Restore
Restore from Backup
./db-backup-restore.sh restore backups/tma_cloud_storage_20260115T020000Z.dumpThe restore process:
- Checks the backup file before touching the database: its SHA-256 checksum, and that
pg_restorecan read it. A file it cannot read is refused with a message. - Asks you to confirm by typing the database name.
- Stops the
appandworkerservices when it runs against the Compose project, so nothing writes during the restore. With a host orDB_CONTAINERdatabase, stop the API and the worker yourself first. - Restores into a new scratch database (
tma_restore_<TIMESTAMP>) in--single-transactionmode. Your current database is not touched yet. - Only when that worked, renames your current database aside and renames the restored one into its place. It then drops the old one. If the rename fails, the old database is put back.
- Runs
ANALYZEto update query planner statistics. - Reports table and row counts so you can check the result.
- Clears the Redis cache of the Compose project with
FLUSHDBon the database inREDIS_URL, because cached rows from before the restore may no longer exist. Redis holds only cache entries and event messages. With a host orDB_CONTAINERdatabase, runFLUSHDByourself or wait 10 minutes for the entries to expire. - Starts the services it stopped. It does this even when the restore fails.
If the restore fails at any step, the script says so and your database keeps its data. It also drops the scratch database. While it runs, the server needs enough free disk space for two copies of the database.
The restored data opens only with the encryption key file from the time of the backup or a later one, since rotation keeps older key versions.
Verify a Backup
./db-backup-restore.sh verify backups/tma_cloud_storage_20260115T020000Z.dumpChecks the SHA-256 checksum against the .meta file and inspects the dump TOC without restoring.
List Backups
./db-backup-restore.sh listLists all .dump files in the backups/ directory with file size and date.
Configuration
The script reads these keys from .env in its directory (or the repository root for scripts/db-backup-restore.sh, or TMA_DIR). It reads them as plain values and never runs .env as a shell script. Variables already set in the environment take precedence. The password reaches pg_dump and docker exec through the environment, so it does not appear in the process list.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | - | Database to back up or restore (required) |
REDIS_URL | - | Redis password and database, to clear the cache |
DB_CONTAINER | - | PostgreSQL container name |
BACKUP_RETAIN_COUNT | 10 | Backups to keep before pruning (1 or more) |
The script splits DATABASE_URL into user, password, host, port, and database, and percent-decodes them. With the host's client tools, these URL parameters are passed on as libpq environment variables: sslmode, sslrootcert, sslcert, sslkey, sslcrl, sslnegotiation, connect_timeout, application_name, options, and target_session_attrs. Inside a container it connects over the local socket as the user in the URL.
File Backups
The backup script only covers the PostgreSQL database. It does not back up uploaded files or folders.
You must back up file storage separately:
S3-compatible
Use your storage vendor's replication or snapshot tools. The application stores object keys in the database; the bucket holds the blobs. Both must be backed up together to stay in sync.
Backup Schedule
Recommended cron schedule for automated backups:
# Daily database backup at 02:00 UTC
0 2 * * * cd /path/to/tma-cloud && ./db-backup-restore.sh backup >> /var/log/tma-backup.log 2>&1Related Topics
- Database Schema - Table structure
- Environment Variables - Full variable reference
- CLI Commands - All available commands
- Storage Management - Storage overview
Last updated on