> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mob.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Backups

> What an instance loses with its database, and how to get it back.

Postgres writes to the `pg_data` named volume declared in `docker-compose.yml`.
That volume outlives container recreation, `docker compose up --build`,
`docker compose down`, host reboots, and upgrades of the `pgvector/pgvector`
image. Two things remove it: `docker compose down -v`, and losing the machine.
The same command takes `caddy_data` with it, so a rebuilt instance refetches
its TLS certificates and spends part of the Let's Encrypt issuance limit for
your domain.

## What gets backed up

These records exist only in this database, and a backup is the only way to
get them back:

* accounts and platform identities
* installations and grants
* registered OAuth clients
* channel write policies
* write limits and rate tiers
* encrypted Slack credentials, tokens issued to your instance specifically

The Discord message index and its embeddings are rebuilt rather than
restored. A fresh instance runs the backfill again:

* it re-fetches message history from Discord
* it generates new embeddings for each message chunk
* it reads image attachments again through the vision model

The rebuild takes time proportional to the amount of history, and the
embedding and image-reading calls are billed again.

Losing the identity and policy records produces the failure
`.env.example` already warns about for changing `MOB_BASE_URL` after
launch: your instance is its own
OAuth issuer, so every connected agent presents a grant against an identity
that no longer exists and stops working at the next request. Recovery runs
through the original install path, with an owner reinstalling the Discord
and Slack apps and every member reconnecting their agent and reauthorizing
Slack. Discord and Slack themselves are unaffected.

## The backup and the .env are one artifact

`slack_installation_credentials` and `slack_user_authorizations` store
ciphertext. The key that reads them is `MOB_CREDENTIAL_ENCRYPTION_KEY`, which
lives in `.env` and never enters the database. Restoring a backup next to a
different key produces rows no one can decrypt, and every affected
installation has to reauthorize with Slack before search works again. Back up
`.env` whenever you back up the database and keep the pair together.

## Creating a backup

```bash theme={null}
docker compose exec -T postgres \
  pg_dump -U mob -d mob --clean --if-exists \
  | gzip > "mob-$(date +%F).sql.gz"
```

`-T` disables TTY allocation, which is what lets the same line run from cron.

## Restoring

Restore before the api starts. The api applies any migration file not yet
recorded in `schema_migrations` on boot, so a backup from an older release
lands on a stopped stack and gets brought forward by the next start.

```bash theme={null}
docker compose stop api            # no-op on a fresh machine
docker compose up -d postgres
gunzip -c mob-2026-07-29.sql.gz \
  | docker compose exec -T postgres \
      psql -U mob -d mob --single-transaction --set ON_ERROR_STOP=on
docker compose up -d
```

The target database needs the pgvector extension available. The
`pgvector/pgvector` image ships it; a plain `postgres` image does not.

Test the restore once against a throwaway machine before you need it. None
of these failures is visible until you try:

* a missing pgvector extension
* a role mismatch
* an encryption key that no longer matches the ciphertext

## Getting the backup off the machine

A backup stored on the server covers a bad upgrade or a mistaken delete, and
a machine failure destroys the backup together with the volume it protects.
mob ships no backup service in `docker-compose.yml` because a container
writing backups beside that volume sits in the same failure domain. Two
arrangements leave that domain:

* point `MOB_DATABASE_URL` at a managed Postgres with point-in-time
  recovery and remove the `postgres` service from the compose file
* keep the local database and pipe each backup to object storage with
  restic or rclone, storing `.env` in the same repository
