Backup and Restore
Face Matcher keeps its state in two Docker volumes and a handful of files in the deployment directory. Backing up means copying those with the stack quiescent; restoring means putting them back on a host with the same package version, then letting start.sh migrate if the version is newer. Practise the restore on a test host before you depend on it.
| What | Where | Contains |
|---|---|---|
| PostgreSQL | volume face-matcher-dependencies_pgsqldata, service pgsql, database facematcher | Watchlists, members, face templates, cameras, edge streams, detections, match results, settings |
| SeaweedFS (S3) | volume face-matcher-dependencies_seaweedfsdata, container seaweedfs, bucket face-matcher | Enrollment images, face and object crops, full frames |
| RabbitMQ | no named volume | Transient messages plus the watchlist update-log stream, which is rebuilt from the database (see below) |
| Deployment files | .env, .env.station, docker-compose.override.yml, secrets/iengine.lic, branding/station/, any masking.png or certificate files | Configuration and license; back them up with every change |
Back up
Stop the platform services so nothing writes during the copy, but leave the dependencies running for the database dump:
docker compose down
mkdir -p backup
Dump PostgreSQL with pg_dump in custom format; it is consistent, compressed and restorable across minor PostgreSQL versions:
docker compose -f dependencies/docker-compose.yml exec -T pgsql \
pg_dump -U postgres -Fc facematcher > backup/facematcher-$(date +%F).dump
Then stop the dependencies too and copy the SeaweedFS volume as an archive (the same command with pgsqldata gives you a raw copy of the database files as a second line of defence, restorable only into the same PostgreSQL major version):
docker compose -f dependencies/docker-compose.yml down
docker run --rm \
-v face-matcher-dependencies_seaweedfsdata:/volume:ro \
-v "$(pwd)/backup":/backup alpine \
tar czf /backup/seaweedfsdata-$(date +%F).tar.gz -C /volume .
./start.sh
Copy backup/ together with the deployment files to storage outside the host. For routine backups without downtime, pg_dump alone can run while the stack is up; the S3 archive is then slightly newer than the dump, which is harmless (orphaned images are cleaned up, missing ones show as empty in Station).
Restore
On the target host, install the same package version (or newer), put the deployment files and the license in place and stop everything:
./stop.sh
Restore the database. Start only PostgreSQL, recreate the database and load the dump:
docker compose -f dependencies/docker-compose.yml up -d pgsql
docker compose -f dependencies/docker-compose.yml exec pgsql \
psql -U postgres -c "DROP DATABASE IF EXISTS facematcher;" -c "CREATE DATABASE facematcher;"
docker compose -f dependencies/docker-compose.yml exec -T pgsql \
pg_restore -U postgres -d facematcher --no-owner < backup/facematcher-YYYY-MM-DD.dump
docker compose -f dependencies/docker-compose.yml down
Restore the S3 volume by emptying it and unpacking the archive; use only data from one backup set, never a mix of old and new:
docker run --rm \
-v face-matcher-dependencies_seaweedfsdata:/volume \
-v "$(pwd)/backup":/backup alpine \
sh -c "rm -rf /volume/* && tar xzf /backup/seaweedfsdata-YYYY-MM-DD.tar.gz -C /volume"
Start the stack. start.sh runs the database migration, so a dump from an older package version is upgraded on the way:
./start.sh
If you use edge watchlist synchronization, rebuild the update-log stream so devices resynchronize from the restored data (Edge Watchlist Synchronization):
docker compose down
./populate-wl-update-log-stream.sh
docker compose up -d
Verify
Check that every service is up with docker compose ps and the health endpoints (Monitoring and Logs), then confirm the data: GET http://localhost:8098/api/v1/Watchlists lists the expected watchlists, a watchlist member opened in Station shows its enrollment image (proves the S3 restore), the event history shows past events with pictures, and a test identification against a known member matches. A restored deployment carries the old license binding only if the hardware ID is the same; on new hardware obtain a new iengine.lic first (Get a License).