Skip to main content

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.

WhatWhereContains
PostgreSQLvolume face-matcher-dependencies_pgsqldata, service pgsql, database facematcherWatchlists, members, face templates, cameras, edge streams, detections, match results, settings
SeaweedFS (S3)volume face-matcher-dependencies_seaweedfsdata, container seaweedfs, bucket face-matcherEnrollment images, face and object crops, full frames
RabbitMQno named volumeTransient 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 filesConfiguration 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).