Backups and upgrades
Your data is one SQLite database and a directory of uploads.
Backup and restore
Because the compose file uses bind mounts, your data is an ordinary directory.
# Back up
docker compose stop
tar czf kothai-backup-$(date +%F).tar.gz data/
docker compose start
# Restore
docker compose down
rm -rf data/
tar xzf kothai-backup-2026-08-17.tar.gz
docker compose up -dStopping first matters. kothai.db runs in WAL mode, so a live database is really three files (kothai.db, -wal, -shm) that need to be copied together in a consistent state.
Any directory-based backup tool (restic, Borg, Time Machine, a NAS snapshot) can point at ./data directly.
Without stopping the container
Settings → YOUR DATA → Download backup, or the endpoint directly:
curl -fO -J http://localhost:5173/api/backupWith KOTHAI_PASSWORD set, that answers 401. Log in first and send the session cookie:
curl -fs -c kothai.cookies -H 'Content-Type: application/json' \
-d '{"password":"your-password"}' http://localhost:5173/api/login
curl -fO -J -b kothai.cookies http://localhost:5173/api/backupYou get one .tar.gz holding the whole library: the database and every uploaded image. The database part uses SQLite's VACUUM INTO, which reads one consistent snapshot and writes a fresh, compacted file. No stopping, no three-file dance. Use it on a PaaS where you can't stop the container, or whenever you just want a copy right now.
It briefly needs free disk space equal to the database's size, and refuses while an import is running. Credentials (the AI key, the Telegram bot token) are never in it.
Automatic backups
Kothai also backs itself up, once a day, to data/backups/. Each one is the same file Download backup gives you. The newest 5 are kept, so budget about five times your library's size for them.
Settings → YOUR DATA → Automatic backups shows when the last one ran, lists every kept file with a Download link, and turns them off. Turn them off if something else already backs up data/ (restic, a NAS snapshot), or disk space is tight.
- It never fills the disk. A backup that would leave less than 256 MB free is skipped, and the reason is shown in Settings.
- Failures are reported. If one fails and a Telegram bot is connected, Kothai messages you once, then retries every hour.
- It is not off-site. These copies live on the same disk as your library and are lost with it. Download one now and then, especially on a PaaS, where the volume has no other way out.
A restore also saves the library it replaces here (before-restore-….tar.gz, newest 3 kept).
Google Drive
Settings → YOUR DATA → Google Drive → Connect Google Drive. Kothai shows a code; enter it at google.com/device on any device and approve. No redirect back to Kothai is involved, so this works the same on localhost, a tailnet or a PaaS.
After that, each daily backup is also copied to a Kothai Backups folder in your Drive, and the newest 5 are kept there.
- Kothai sees only its own files. It asks for Google's
drive.filepermission: the files it created, nothing else in your Drive. - The files are ordinary backups, not encrypted: the same
.tar.gzas Download backup. Anyone who can open your Drive can open them. - Restoring on a new machine: install Kothai, connect the same Google account, and pick a backup from Show backups on Drive. It goes through the same restore as an uploaded file, and saves the library it replaces first.
- Disconnecting revokes Kothai's access and forgets it. The backups stay in your Drive.
- If a copy fails (Google unreachable, access revoked), Settings says why, the connected Telegram bot tells you once, and Kothai retries every hour.
Needs KOTHAI_GOOGLE_CLIENT_ID and KOTHAI_GOOGLE_CLIENT_SECRET. Without them the row says it isn't set up.
Creating the Google client
Once per Google Cloud project, not per install:
- In Google Cloud Console, create a project and enable the Google Drive API.
- Set up the OAuth consent screen (Google Auth Platform → Branding and Audience): user type External; add the scopes
.../auth/drive.file,openidandemail. - Publish it to "In production". While it is in Testing, Google expires every refresh token after 7 days, and Drive backups would quietly disconnect each week.
drive.fileis a non-sensitive scope, so publishing needs no security review. - Credentials → Create OAuth client ID → application type TVs and Limited Input devices.
- Set the client ID and secret as
KOTHAI_GOOGLE_CLIENT_IDandKOTHAI_GOOGLE_CLIENT_SECRET. A device client's secret is not confidential: Google treats these as public clients, since the secret ships inside every copy of the app.
Restoring a backup
Settings → YOUR DATA → Restore, and choose the file. Or the endpoint directly:
curl -f -H 'Content-Type: application/octet-stream' \
--data-binary @kothai-backup-2026-09-26.tar.gz http://localhost:5173/api/restoreIt happens while Kothai keeps running:
- Notes, spaces, chats and uploads come from the backup.
- Model settings stay as they are on this install, since they describe this machine. If the backup's notes were embedded with a different model, they are re-embedded in the background.
- Your current library is saved first, to
data/backups/before-restore-<time>.tar.gz. Restoring the wrong file is undone by downloading that one from Automatic backups and restoring it.
Restore takes a file from Download backup. A tarball of data/ made the manual way above is restored the manual way too.
A backup from an older version (a bare .db file) restores too. It carries no uploads, so the ones already in data/uploads are left alone.
A backup is also an ordinary archive: tar -xzf kothai-backup-….tar.gz gives you kothai.db and uploads/, which is exactly the data/ directory's shape.
Upgrading
docker compose pull
docker compose up -dYour data and downloaded models are untouched.
Last updated on