# Backups and upgrades

> What to back up on a Kribble server, how to restore it, and how to upgrade and roll back

A Kribble server keeps its data in PostgreSQL and the file storage, and a search index in Meilisearch. Back up the database, the files and your configuration. A backup of Meilisearch is optional, because Kribble can rebuild the index from the database.

## What to back up

| What | Where | Why |
| --- | --- | --- |
| PostgreSQL | the `kribble` database | Accounts, organisations, spaces, notes and their history, comments, access tokens, published-site settings. Everything except files. |
| File storage | `[storage].dir` (disk), or the buckets `[storage.s3].bucket` and `public_bucket` | Uploaded images, attachments and avatars, and the built sites of the built-in target |
| Meilisearch (optional) | `/var/lib/meilisearch/data`, or the `meili_data` volume | The search index. Kribble can rebuild it from the database, see below. |
| Configuration | `/opt/kribble/config.toml`, systemd drop-ins, Docker `.env` and `docker-compose.override.yml`, the reverse proxy config | Holds every secret. Without `jwt_secret` and `refresh_secret` everyone has to sign in again; without the storage keys you can't reach the files. |
| Publish target keys | for example `/etc/kribble/keys` | SSH keys and `known_hosts` for `[[publish_targets.ssh]]` |

You don't need to back up:

- **Redis.** It holds only caches.
- **`/opt/kribble/downloads`.** It comes from the release tarball.
- **Sites on external targets.** They are built again from the notes.

:::callout{type="info"}
**The search index can be rebuilt.** Everything in Meilisearch comes from the database. If you lose the Meilisearch data, click **Rebuild index** in the admin panel, or restart `kribble-web`, which finds the index empty and rebuilds it from the database in the background. Until the rebuild is done, search misses the notes it hasn't reached yet. A backup of Meilisearch only saves that wait, which matters on a server with many notes.
:::

### Keeping the database and files consistent

Files that no note or kept version uses any more are deleted by `kribble-builder` 24 hours after they were released, and uploads never saved into a note after 1 hour. So:

1. Dump the database first.
2. Then copy the files, within 24 hours of the dump.

Every file the dump refers to is then still in the copy.

## Making a backup

:::tabs{group="install"}
::tab{label="Linux host"}
```sh
# Database (custom format, compressed)
sudo -u postgres pg_dump -Fc kribble > /backup/kribble-$(date +%F).dump

# Files, disk backend
sudo tar czf /backup/kribble-files-$(date +%F).tar.gz -C /opt/kribble data

# Files, S3 backend (rclone remote "s3" configured for your provider)
rclone sync s3:kribble-uploads /backup/kribble-uploads
rclone sync s3:kribble-public  /backup/kribble-public

# Search index (optional): stop Meilisearch briefly and copy its data
sudo systemctl stop meilisearch
sudo tar czf /backup/meili-$(date +%F).tar.gz -C /var/lib/meilisearch data
sudo systemctl start meilisearch

# Configuration
sudo cp /opt/kribble/config.toml /backup/config-$(date +%F).toml
```
::
::tab{label="Docker Compose"}
```sh
cd /opt/kribble-docker

# Database
docker compose exec -T db pg_dump -U kribble -Fc kribble > /backup/kribble-$(date +%F).dump

# Files, disk backend (the kribble_files volume)
docker run --rm -v "$(basename "$PWD")_kribble_files:/data" -v /backup:/backup alpine \
  tar czf /backup/kribble-files-$(date +%F).tar.gz -C /data .

# Search index (optional): stop Meilisearch briefly and copy the volume
docker compose stop meilisearch
docker run --rm -v "$(basename "$PWD")_meili_data:/meili_data" -v /backup:/backup alpine \
  tar czf /backup/meili-$(date +%F).tar.gz -C /meili_data .
docker compose start meilisearch

# Configuration
cp config.toml .env /backup/
```

Compose names volumes `<project>_<volume>`, where the project defaults to the directory name. `docker volume ls` shows the real names.
::
:::

While Meilisearch is stopped, notes still save, but full-text search fails and the notes changed meanwhile aren't added to the index. Copy it at a quiet time, or use Meilisearch's own dump feature, which works while it runs. If notes changed while it was stopped, click **Rebuild index** on the **System** tab of the admin panel afterwards (see [Troubleshooting](../running-a-server/troubleshooting.md)).

## Restoring

:::steps
::step
Stop Kribble

---
```sh
sudo systemctl stop kribble-web kribble-builder
```

With Docker: `docker compose stop kribble-web kribble-builder`.
::
::step
Restore the database

---
Into an empty database owned by `kribble`:

```sh
sudo -u postgres dropdb kribble
sudo -u postgres createdb kribble -O kribble
sudo -u postgres pg_restore --no-owner --role=kribble -d kribble /backup/kribble-2026-10-01.dump
```

With Docker:

```sh
docker compose exec db dropdb -U kribble kribble
docker compose exec db createdb -U kribble kribble
docker compose exec -T db pg_restore -U kribble --no-owner -d kribble < /backup/kribble-2026-10-01.dump
```
::
::step
Restore the files

---
Put the disk directory back (`sudo tar xzf … -C /opt/kribble`, then `sudo chown -R kribble:kribble /opt/kribble/data`), or sync the buckets back with `rclone sync`.
::
::step
Restore Meilisearch, or start it empty

---
To restore a backup, stop Meilisearch, replace its data directory or volume with the backup from the same time as the database, and start it again. Use the same Meilisearch version that wrote the backup.

Without a backup, start Meilisearch with an empty data directory or volume. `kribble-web` rebuilds the index when it starts in the next step.
::
::step
Restore the configuration and start

---
Put back `config.toml` (with the same `jwt_secret`, `refresh_secret`, Meilisearch key and storage credentials), then:

```sh
sudo systemctl start kribble-web kribble-builder
```
::
:::

A restored search index rarely matches the database exactly: search may miss notes, or find them by older text. Once Kribble runs, click **Rebuild index** on the **System** tab of the admin panel to bring it up to date. Notes that no longer exist are never shown, as results are checked against the database.

Sites on the built-in target come back with the file storage. Sites on external targets keep whatever was last deployed there. To bring them up to date, rebuild them (`kribble redeploy`, or **Publish** in the web app).

## Upgrading

Both binaries create and update the database tables when they start (`[database].auto_migrate`, on by default). There is no separate migration step, so always run `kribble-web` and `kribble-builder` from the same release.

:::steps
::step
Read the release notes and back up

---
Make a database backup right before upgrading. It is your way back if the new version changes the database.
::
::step
Install the new release

---
On a Linux host, unpack the new tarball and run `sudo bash install.sh`. It stops both services, keeps the old binaries as `/opt/kribble/kribble-web.bak` and `kribble-builder.bak`, installs the new ones and the new `downloads/`, and starts the services.

With Docker, build or load the new `kribble:latest` and run `docker compose up -d`.
::
::step
Check

---
```sh
journalctl -u kribble-web -n 50       # or: docker compose logs --tail 50 kribble-web
curl -s https://kribble.example.com/api/v1/config
```

The version in `/api/v1/config` is the new one. Connected CLIs report the mismatch and update with `kribble update`; desktop apps update themselves if the release includes them.
::
:::

If the new release indexes notes differently from the old one, `kribble-web` rebuilds the search index in the background after it starts. The log shows `search: rebuilding the index`, then `search: index rebuilt` with the number of notes and the time it took. Search works meanwhile, but can miss notes or headings until the rebuild is done. With several `kribble-web` processes, each one rebuilds; the result is the same.

If a migration fails, the binary logs `Database migration failed` and exits. systemd restarts it every 5 seconds, so the log repeats until you fix the cause or roll back.

With `auto_migrate = false`, nothing changes the schema. Only use it if you apply the schema changes some other way.

### Upgrading PostgreSQL, Meilisearch and Redis

- **PostgreSQL**: use a dump and restore, or `pg_upgrade`, as for any PostgreSQL database. Kribble needs 16 or newer.
- **Meilisearch**: Kribble needs 1.12 or newer. Its data directory is tied to the Meilisearch version that wrote it. Either follow Meilisearch's upgrade instructions (export a dump with the old version, import it with the new one), or start the new version with an empty data directory (with Docker, remove the `meili_data` volume) and restart `kribble-web`, which rebuilds the index. `docker-compose.yml` pins `getmeili/meilisearch:v1.12`; change the tag deliberately.
- **Redis**: replace it at will. Kribble refills the caches.

## Rolling back

Kribble's automatic migrations add tables, columns and indexes and don't remove any. An older binary therefore often runs on a newer schema, but this isn't guaranteed. The safe way back is the binaries and the database backup together:

:::tabs{group="install"}
::tab{label="Linux host"}
```sh
sudo systemctl stop kribble-web kribble-builder
cd /opt/kribble
sudo cp kribble-web.bak kribble-web
sudo cp kribble-builder.bak kribble-builder
# restore the database backup made before the upgrade (see Restoring), then:
sudo systemctl start kribble-web kribble-builder
```

`install.sh` keeps only one `.bak` per binary, and the `downloads/` directory isn't kept. To get the old CLI downloads back too, run the old release's `install.sh` instead.
::
::tab{label="Docker Compose"}
Tag images before you upgrade (`docker tag kribble:latest kribble:<old-version>`). To roll back, stop the Kribble containers, restore the database, point `kribble:latest` back at the old image (`docker tag kribble:<old-version> kribble:latest`) and run `docker compose up -d`.
::
:::

Changes made after the backup are lost when you restore it. If you only roll back the binaries and keep the newer database, test it on a copy first.
