Appearance
Back up, upgrade and rotate the key
Back up
One Postgres database holds everything: your keys, your monitors and your inbox. A backup is two things, and both are needed:
Dump the database:
bash
docker compose exec -T postgres pg_dump -U intentwatch intentwatch \
| gzip > signalscout-$(date +%F).sql.gzCopy .env too. The keys you pasted on a screen are stored encrypted with ENCRYPTION_KEY, and that key is in .env, not in the dump. A dump restored without it holds keys nobody can read, and each one must be pasted again. Keep .env somewhere the dump is not.
Restore into an empty database:
bash
gunzip -c signalscout-2026-09-22.sql.gz \
| docker compose exec -T postgres psql -U intentwatch intentwatchThe database name
intentwatch is the old product name. It stays in the defaults, so an existing database keeps its data. If you changed POSTGRES_USER or POSTGRES_DB, use your values in these commands.
Upgrade
bash
docker compose exec -T postgres pg_dump -U intentwatch intentwatch | gzip > backup.sql.gz
docker compose pull
docker compose up -dBack up first. A release can change the database schema, and that change cannot be undone.
The migrate container updates the database before the app starts. Watch it with docker compose logs migrate. It prints migrations applied to intentwatch when it is done.
If you pinned a version, change SIGNALSCOUT_IMAGE in .env to the new one first. Read what changed on the Releases page before you do.
If the upgrade fails
If migrate fails, the app does not start, on purpose: a half-updated database that serves requests is worse than one that serves none.
- Read
docker compose logs migrate. It names the step that failed. - To go back, restore the backup, set
SIGNALSCOUT_IMAGEto the version you had, and rundocker compose up -d.
Always restore when you go back. The old version running on a database that the new version already changed is not a state anybody has tested.
Rotate the encryption key
Change ENCRYPTION_KEY if it may have leaked. Every stored key is re-encrypted in one step, so no key is ever half on the old key and half on the new one.
Back up the database, and keep the
.envthat holds the old key.Make a new key:
openssl rand -base64 32Stop the app:
docker compose stop app workerRe-encrypt, with the old key still in
.env:bashNEW_ENCRYPTION_KEY='<the new key>' docker compose run --rm -e NEW_ENCRYPTION_KEY \ app node packages/pipeline/dist/secrets/rotate-cli.jsIt prints how many keys it re-encrypted.
Put the new key in
ENCRYPTION_KEYin.env.Start the app:
docker compose up -d
The count in step 4 must equal the number of keys you stored: provider keys, model keys and webhook secrets together. If anything fails, restore the backup from step 1 and start again. Do not edit rows by hand.
If the key is lost, the stored keys are gone. There is no copy, by design. Paste them again from each provider's dashboard.