Backup and Recovery
On a single-machine deployment, all of FinClaw's stateful data lives in Docker named volumes (the
chatkit_*family). This page gives the simplest viable backup procedure: stop → archive every volume → start.
1. What to Back Up
1.1 Configuration Files
| Path | What it holds |
|---|---|
deploy/.env | LLM credentials, JWT keys, internal tokens, concurrency settings |
deploy/config/ | services.yaml, ai-infra.yaml, idp.yaml, etc. |
deploy/docker-compose.override.yml (if any) | Custom port mapping or other overrides |
1.2 Docker Volumes
The chatkit_* volumes — all declared external: true in docker-compose.yml:
| Volume | Contents |
|---|---|
chatkit_postgres_data | PostgreSQL data (identity / inbox / persona / skill / document / pulse) |
chatkit_timescaledb_data | TimescaleDB data (conversation-store history) |
chatkit_ai_infra_data | AI runtime state (audit logs, memory index, sessions, mcp-servers.yaml) |
chatkit_workspaces | User workspaces (installed skills, memory files) |
chatkit_skills_data | Platform skills data (populated by skills-init) |
chatkit_persona_uploads | Persona service uploads |
chatkit_identity_uploads | Identity service uploads (avatars, etc.) |
chatkit_doc_toolkit_cache | Doc toolkit cache |
chatkit_nats_data | NATS JetStream (event streams, short-lived) |
chatkit_redis_data | Redis cache and coordination state |
chatkit_restate_data | Restate timers and cron state |
chatkit_tei_models | TEI model cache |
Simple rule for single-machine: archive every
chatkit_*volume. Don't try to cherry-pick — one missed volume can mean data loss.
2. Backup
Step 1 — Stop the services
Backing up volumes while containers are running yields an inconsistent snapshot (especially for PostgreSQL). Stop first.
cd /data/chatkit-offline-release-1.0.xx-linux-amd64/deploy
./scripts/fleet.sh basic down
downonly stops the containers; it does not delete volumes.
Step 2 — Archive config + volumes
# Prepare a backup directory
BACKUP_DIR=/data/backups/finclaw-$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# 1) Configuration files
tar -czf "$BACKUP_DIR/deploy-config.tar.gz" \
-C /data/chatkit-offline-release-1.0.xx-linux-amd64/deploy \
.env config docker-compose.override.yml 2>/dev/null || true
# 2) Volumes — one-shot container archives each volume's contents
for vol in $(docker volume ls -q | grep '^chatkit_'); do
echo ">> backing up volume: $vol"
docker run --rm \
-v "$vol":/source:ro \
-v "$BACKUP_DIR":/backup \
alpine \
tar -czf "/backup/${vol}.tar.gz" -C /source .
done
# 3) Record volume list + release metadata
docker volume ls --filter "name=chatkit_" --format '{{.Name}}' > "$BACKUP_DIR/volumes.txt"
echo "release_dir=$(realpath /data/chatkit-offline-release-1.0.xx-linux-amd64)" > "$BACKUP_DIR/meta.txt"
date -u +"backup_time=%Y-%m-%dT%H:%M:%SZ" >> "$BACKUP_DIR/meta.txt"
# 4) Inspect the backup
ls -lh "$BACKUP_DIR"Step 3 — Start the services
./scripts/fleet.sh basic up -d
./scripts/doctor.shBackup Layout
/data/backups/finclaw-20260625-030000/
├── deploy-config.tar.gz # .env + config/ + override
├── chatkit_postgres_data.tar.gz
├── chatkit_timescaledb_data.tar.gz
├── chatkit_ai_infra_data.tar.gz
├── chatkit_workspaces.tar.gz
├── chatkit_skills_data.tar.gz
├── chatkit_persona_uploads.tar.gz
├── chatkit_identity_uploads.tar.gz
├── chatkit_doc_toolkit_cache.tar.gz
├── chatkit_nats_data.tar.gz
├── chatkit_redis_data.tar.gz
├── chatkit_restate_data.tar.gz
├── chatkit_tei_models.tar.gz
├── volumes.txt # list of volume names
└── meta.txt # release path + backup timestamp3. Restore
Step 1 — Stop services and remove existing volumes
cd /data/chatkit-offline-release-1.0.xx-linux-amd64/deploy
./scripts/fleet.sh basic down
# Remove all chatkit_* volumes (confirm you want to roll back!)
docker volume ls -q | grep '^chatkit_' | xargs -r docker volume rm⚠️ This drops any data not in the backup. Double-check before executing.
Step 2 — Restore configuration
BACKUP_DIR=/data/backups/finclaw-20260625-030000 # replace with your backup dir
tar -xzf "$BACKUP_DIR/deploy-config.tar.gz" \
-C /data/chatkit-offline-release-1.0.xx-linux-amd64/deployStep 3 — Restore volumes
# Restore each volume: create an empty volume, then unpack the tar into it
for tarball in "$BACKUP_DIR"/chatkit_*.tar.gz; do
vol=$(basename "$tarball" .tar.gz)
echo "<< restoring volume: $vol"
docker volume create "$vol" >/dev/null
docker run --rm \
-v "$vol":/target \
-v "$BACKUP_DIR":/backup:ro \
alpine \
tar -xzf "/backup/${vol}.tar.gz" -C /target
doneStep 4 — Start and verify
./scripts/fleet.sh basic up -d
./scripts/doctor.sh
curl http://127.0.0.1:26100/healthExpected: doctor.sh returns all ✅ and /health returns 200 OK. Allow 2–5 minutes for slow first starts.
