Backups and restore
How backups are taken
The worker backs up the database on the schedule from Settings → Backups — by default every hour at :00 Moscow time. The same page has retention, Telegram delivery, “Back up now”, the list and downloads.
- Contents — the whole database, compressed (
pg_dump -Fc). The file is encrypted withBACKUP_PASSWORDfrom.env(AES-256, compatible withopenssl enc -aes-256-cbc -pbkdf2 -iter 200000 -md sha256). - Location —
BACKUP_HOST_DIR(default/home/deploy/backups/auto), outside the installation folder: updates do not touch it. The file name is Moscow time, e.g.…_2026-10-19_14-00-00_msk.dump.enc. - Retention — all copies for 48 hours, then the last one per day for 14 days and the last one per week for 4 weeks; older than 30 days are deleted. You can cap the number of copies.
- Disk space — with less than 1 GB free, old copies are removed early and you get “Low space for backups”. If there is no space at all, no copy is taken, so the disk is never filled to the point of breaking the database.
- Telegram — the file goes to the chat from Notifications. Over 50 MB — in 45 MB parts (join:
cat FILE.0* > FILE) or just a message, as configured. - Failure — a “Backup error” event in the bell and Telegram, and the “Backups” part in System status.
Backup password
Without BACKUP_PASSWORD nobody can open a copy, including us. To view it: grep ^BACKUP_PASSWORD= .env in the installation folder. Keep it in a password manager, not next to the copies.
Keep copies elsewhere too
Copies on the same server will not help if the server itself is lost. Enable Telegram delivery or download copies from the admin panel regularly.
Verify a copy
Run on the server from the installation folder. The copy is restored into a separate temporary database — the working one is not touched. The user and database names come from docker-compose.yml (POSTGRES_USER, POSTGRES_DB).
F=/home/deploy/backups/auto/BACKUP_NAME.dump.enc
DBU=$(docker compose exec -T db printenv POSTGRES_USER)
export BACKUP_PASSWORD=$(grep '^BACKUP_PASSWORD=' .env | cut -d= -f2-)
docker compose exec -T db createdb -U "$DBU" restore_check
sudo cat "$F" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -md sha256 -pass env:BACKUP_PASSWORD \
| docker compose exec -T db pg_restore -U "$DBU" -d restore_check --exit-on-error
docker compose exec -T db psql -U "$DBU" restore_check -c "select count(*) from orders"
docker compose exec -T db dropdb -U "$DBU" restore_checkIf the order count matches what you expect, the copy works. Do this at least once a month.
Restore
To return the working database to a copy: stop the app, take a safety copy of the current state, recreate the database and load the copy.
F=/home/deploy/backups/auto/BACKUP_NAME.dump.enc
DBU=$(docker compose exec -T db printenv POSTGRES_USER)
DBN=$(docker compose exec -T db printenv POSTGRES_DB)
export BACKUP_PASSWORD=$(grep '^BACKUP_PASSWORD=' .env | cut -d= -f2-)
docker compose stop api worker bot
SAFE=/home/deploy/backups/auto/before-restore-$(date +%F_%H%M).dump
(umask 077; docker compose exec -T db pg_dump -U "$DBU" -Fc "$DBN" > "$SAFE")
test -s "$SAFE" && docker compose exec -T db dropdb -U "$DBU" --force "$DBN"
docker compose exec -T db createdb -U "$DBU" "$DBN"
sudo cat "$F" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -md sha256 -pass env:BACKUP_PASSWORD \
| docker compose exec -T db pg_restore -U "$DBU" -d "$DBN" --exit-on-error --single-transaction
docker compose up -d api worker
docker compose --profile bot up -d bot # if the bot is enabledThe safety copy (before-restore-…) goes into the private backup folder, readable only by its owner; if it was not taken or is empty, the database is not dropped (test -s). It is not encrypted — delete or encrypt it after a successful restore.
Database role passwords live outside the database and do not change on restore. The application role's restricted permissions are restored with the copy.
Open a copy on your computer
Decrypt a copy from Telegram or the admin panel the same way (it asks for the password):
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -md sha256 -in FILE.dump.enc -out FILE.dump
pg_restore --list FILE.dump