killBottleneck is in public beta — cloud and self-host.killBottleneck is in beta.Beta on GitHub →
Skip to content

Backup and restore ​

All data lives in one folder: ./pb_data next to docker-compose.yml — the SQLite database plus uploaded files. Nothing of yours is stored anywhere else, and nothing is stored inside the container.

The bundled script ​

bash
./backup.sh                 # creates kb-backup-YYYY-MM-DD.tgz
./backup.sh restore FILE    # restores data from a backup

Encrypted backups ​

The archive holds everything — maps, user accounts, uploaded files. If it ever leaves the server (a download to your laptop, an off-site copy, any cloud storage), encrypt it: export KB_BACKUP_PASSPHRASE and the script writes kb-backup-YYYY-MM-DD.tgz.gpg instead (symmetric GPG, AES-256).

bash
export KB_BACKUP_PASSPHRASE='a-long-passphrase-of-your-own'
./backup.sh                              # → kb-backup-YYYY-MM-DD.tgz.gpg
./backup.sh restore kb-backup-....tgz.gpg   # same passphrase, or gpg asks for it

restore accepts both plain .tgz and .tgz.gpg, so older unencrypted backups keep working. Keep the passphrase outside the server and outside the repository — a password manager is the right place. There is no recovery: without the passphrase the archive is unreadable for everyone, including you.

By hand ​

Back up by copying the pb_data folder; restore by putting it back. That is genuinely all there is to it.

bash
docker compose down
tar czf kb-backup-$(date +%F).tgz pb_data
docker compose up -d

Take one before a bigger update

Database migrations run automatically on start, and they run forward only. A backup taken before docker compose up -d --build is the difference between a bad afternoon and a bad week.

Verify the backup, do not assume it ​

A backup you have never restored is a hypothesis. Restore it once into a throwaway copy:

bash
mkdir /tmp/kb-restore-test && cd /tmp/kb-restore-test
tar xzf /path/to/kb-backup-2026-07-30.tgz
ls pb_data          # you should see data.db and a storage folder

What is not in the backup ​

.env and docker-compose.override.yml are your configuration, not data — the script does not include them. Keep them somewhere too; without them a restored instance comes up with default ports, no AI, and no Google sign-in.

fair-code — self-hosting and internal use are free, reselling as a hosted service is not.