Backup and Restore
Status: Complete
How to back up your Chatalot instance and restore from backups.
What to Back Up
A complete Chatalot backup includes three components:
| Component | Location (Docker) | Location (Manual) | Contains |
|---|---|---|---|
| Database | postgres_data volume |
PostgreSQL data directory | Users, messages, channels, communities, all metadata |
| Uploaded files | file_storage volume |
./data/files |
All file uploads (encrypted on disk) |
| Secrets | ./secrets/ directory |
./secrets/ directory |
JWT signing keys, .env file |
Warning: If you lose the JWT private key, all existing access and refresh tokens become invalid. Every user will be forced to log in again. Keep backups of
secrets/jwt_private.pemandsecrets/jwt_public.pemsecure.
Database Backup
Using chatalot-snapshot (Recommended for managed-update flow)
The chatalot-snapshot CLI (part of the chatalot-updater crate) produces gzipped pg_dump output plus a sidecar JSON manifest, stored in a dedicated directory with automatic retention. This is the same tool the managed-update system uses before every apply.
# Create a snapshot (label is optional but helpful for out-of-band backups)
chatalot-snapshot create --label pre-upgrade
# List existing snapshots
chatalot-snapshot list
# Verify integrity of a specific snapshot
chatalot-snapshot verify 20260419T120000Z-0.24.6
# Restore (single-attempt replay; requires the app to be stopped)
chatalot-snapshot restore 20260419T120000Z-0.24.6
# Prune to keep only the newest N (default N=5)
chatalot-snapshot prune
Configuration (env vars):
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
(required) | postgres URL, same as chatalot-server |
CHATALOT_SNAPSHOT_DIR |
/var/backups/chatalot |
Where snapshots + sidecar manifests live |
CHATALOT_SNAPSHOT_RETAIN_COUNT |
5 |
prune keeps the newest N |
CHATALOT_ROLLBACK_WINDOW_HOURS |
24 (max 168) |
Rollback UI hides snapshots older than this |
CHATALOT_VERSION |
build-time | Embedded in snapshot IDs |
Security note: the CLI passes the DB password via PGPASSWORD env var (never on the command line) so it's not visible to ps or other processes on the host.
Using pg_dump directly (alternative)
# Docker: dump to a SQL file
docker exec chatalot-db pg_dump -U chatalot --format=custom chatalot > backup_$(date +%Y%m%d_%H%M%S).dump
# Manual: dump from local PostgreSQL
pg_dump -U chatalot --format=custom chatalot > backup_$(date +%Y%m%d_%H%M%S).dump
The --format=custom flag produces a compressed binary format that supports selective restore. For a plain SQL dump:
Verify the Dump
# Check the dump file is valid (custom format)
pg_restore --list backup_20260216_120000.dump | head -20
# Check file size (should be non-zero)
ls -lh backup_*.dump
File Backup
Docker
# Copy files from the Docker volume to a local tar archive
docker run --rm \
-v chatalot_file_storage:/data:ro \
-v $(pwd):/backup \
alpine tar czf /backup/files_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .
Manual
Secrets Backup
Warning: The
.envfile contains the database password and TOTP encryption key. Store secret backups in an encrypted location (e.g., an encrypted USB drive, a password manager, or an encrypted cloud storage bucket).
Full Backup Script
Create scripts/backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="${1:-./backups}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
echo "Backing up database..."
docker exec chatalot-db pg_dump -U chatalot --format=custom chatalot \
> "$BACKUP_DIR/db_${TIMESTAMP}.dump"
echo "Backing up uploaded files..."
docker run --rm \
-v chatalot_file_storage:/data:ro \
-v "$(pwd)/$BACKUP_DIR":/backup \
alpine tar czf "/backup/files_${TIMESTAMP}.tar.gz" -C /data .
echo "Backing up secrets..."
tar czf "$BACKUP_DIR/secrets_${TIMESTAMP}.tar.gz" secrets/ .env
echo "Backup complete:"
ls -lh "$BACKUP_DIR"/*_${TIMESTAMP}*
Automated Backups with Cron
Add a cron job to run backups daily:
# Edit crontab
crontab -e
# Add: run backup at 3 AM daily, keep 30 days of backups
0 3 * * * cd /srv/chatalot && ./scripts/backup.sh ./backups && find ./backups -mtime +30 -delete
Restore Procedure
Restore the Database
# Stop the application (keep PostgreSQL running)
docker compose stop chatalot
# Drop and recreate the database
docker exec chatalot-db psql -U chatalot -c "DROP DATABASE chatalot;"
docker exec chatalot-db psql -U chatalot -c "CREATE DATABASE chatalot OWNER chatalot;"
# Restore from custom format dump
docker exec -i chatalot-db pg_restore -U chatalot -d chatalot < backup_20260216_120000.dump
# Or restore from plain SQL
docker exec -i chatalot-db psql -U chatalot chatalot < backup_20260216_120000.sql
# Start the application
docker compose start chatalot
Restore Uploaded Files
# Stop the application
docker compose stop chatalot
# Clear existing files and restore
docker run --rm \
-v chatalot_file_storage:/data \
-v $(pwd):/backup \
alpine sh -c "rm -rf /data/* && tar xzf /backup/files_20260216_120000.tar.gz -C /data"
# Start the application
docker compose start chatalot
Restore Secrets
# Stop the application
docker compose down
# Restore secrets
tar xzf secrets_20260216_120000.tar.gz
# Verify file permissions
chmod 600 secrets/jwt_private.pem
chmod 644 secrets/jwt_public.pem
chmod 600 .env
# Start the application
docker compose up -d
Testing Backups
Periodically test your backups by restoring to a separate environment:
# Create a test directory
mkdir /tmp/chatalot-restore-test
cd /tmp/chatalot-restore-test
# Start a temporary PostgreSQL instance
docker run -d --name restore-test-db \
-e POSTGRES_DB=chatalot \
-e POSTGRES_USER=chatalot \
-e POSTGRES_PASSWORD=test \
postgres:17
# Wait for it to start
sleep 5
# Restore the dump
docker exec -i restore-test-db pg_restore -U chatalot -d chatalot < /path/to/backup.dump
# Verify the data
docker exec restore-test-db psql -U chatalot -c "SELECT count(*) FROM users;"
docker exec restore-test-db psql -U chatalot -c "SELECT count(*) FROM messages;"
# Clean up
docker rm -f restore-test-db
Backup Storage Recommendations
| Method | Suitable For |
|---|---|
| Local directory | Development, small instances |
| External drive | Small to medium instances |
| Remote server (rsync/scp) | Production instances |
| S3-compatible storage | Large instances, long-term retention |
| Encrypted cloud storage | Sensitive data, compliance requirements |
Example rsync to a remote server:
Next Step
For update procedures, see Updating.