Ir al contenido

Backing up before an upgrade

Administrar un servidor 6 min de lectura Fuente en codefloeSolo en inglés

En esta página

Every upgrade instruction in this repo starts with "back up your database and files first." This page is the simple, step-by-step version of how to actually do that — pick your path:

A backup does not move a database to a new PostgreSQL major version. To move an existing Docker install from PostgreSQL 15, 16 or 17 to 18, follow Upgrading PostgreSQL instead: it takes the same dump as below, at a point where nothing writes to the database any more, and restores it into the new version.

Both paths back up the same three things:

  1. The Postgres database — your notes, users, follows, everything relational.
  2. The files directory — locally-stored media (avatars, attachments, emoji), unless you use S3/object storage, in which case that data lives with your storage provider instead and this step is not needed.
  3. The whole .config directory — default.yml, and for Docker also docker.env. It's small, but painful to reconstruct from memory if lost.

You do not need to stop Catodon to take the backup — pg_dump and copying the files directory are both safe to run against a live instance — but doing it right before you start the upgrade means the backup actually reflects what you're about to upgrade. Restoring is a different story: do that with the stack stopped (see the restore sections below).

Docker

From the directory containing your compose.yml:

STAMP=$(date +%F)

# 1. Database dump (custom format, works with pg_restore later). Run the
#    dump *inside* the container with `sh -c` so $POSTGRES_USER/$POSTGRES_DB
#    expand from the container's own environment (loaded via docker.env),
#    not from whatever happens to be set in your host shell. -T disables TTY
#    allocation, which matters here because -Fc output is binary.
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -Fc "$POSTGRES_DB"' > "catodon-db-backup-$STAMP.dump"

# Verify it's not empty and is a real dump before trusting it:
test -s "catodon-db-backup-$STAMP.dump" && pg_restore --list "catodon-db-backup-$STAMP.dump" | head

# 2. Files directory (skip if you use S3/object storage). The container runs
#    as UID/GID 991, so files on disk may be owned by 991:991 — a plain `cp`
#    as your own user still works for a backup copy, just don't be surprised
#    by the ownership if you inspect it.
cp -a files "files.backup-$STAMP"

# 3. Config (includes default.yml and docker.env)
cp -a .config ".config.backup-$STAMP"

The $STAMP suffix matters: running these commands again later (e.g. before your next upgrade) would otherwise overwrite the previous backup, or worse, nest one backup inside another (cp -a files files.backup run twice produces files.backup/files).

Move the three backups off the host — another machine, object storage, anywhere but the same disk — for example:

rsync -a "catodon-db-backup-$STAMP.dump" "files.backup-$STAMP" ".config.backup-$STAMP" user@backup-host:/backups/catodon/

A backup that lives only on the disk you're about to upgrade doesn't survive that disk failing.

Restoring (Docker)

Stop the app before restoring — a live web container holds connections to the database that --clean will fail to drop, and restoring config or files underneath a running container will be inconsistent with what it has open:

docker compose stop web

docker compose exec -T db sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' < catodon-db-backup-<STAMP>.dump

--if-exists suppresses the "does not exist, skipping" noise --clean otherwise prints for objects that aren't there yet on a fresh database — that output is expected and not a sign anything went wrong.

Restore files and config by replacing the current directories with the backup (note .config is mounted read-only into the container, so this has to happen from the host, stack stopped):

rm -rf files && cp -a "files.backup-<STAMP>" files
rm -rf .config && cp -a ".config.backup-<STAMP>" .config

docker compose up -d

Bare metal

STAMP=$(date +%F)

# 1. Database dump
sudo -u postgres pg_dump -Fc catodon > "catodon-db-backup-$STAMP.dump"
test -s "catodon-db-backup-$STAMP.dump" && pg_restore --list "catodon-db-backup-$STAMP.dump" | head

# 2. Files directory (skip if you use S3/object storage). Find the real path
#    first — don't assume it:
grep mediaDirectory .config/default.yml
#    If that line is absent or commented out, the default is a `files`
#    directory next to your repo checkout, NOT /var/lib/catodon (that path
#    only appears as a commented-out example in .config/example.yml). Use
#    whatever the grep above actually shows; the two commands below cover
#    both cases — run the one that matches your setup.
cp -a files "files.backup-$STAMP"                          # default location
sudo cp -a /var/lib/catodon "/var/lib/catodon.backup-$STAMP"  # only if mediaDirectory is set to it

# 3. Config
cp -a .config ".config.backup-$STAMP"

If you're unsure what database name/user Catodon connects with, check the db: block of .config/default.yml — the values there are what you pass to pg_dump/pg_restore (-U, and the database name as the last argument).

As with Docker, copy all backups off the host before proceeding with the upgrade — e.g. rsync -a catodon-db-backup-$STAMP.dump files.backup-$STAMP .config.backup-$STAMP user@backup-host:/backups/catodon/.

Restoring (bare metal)

Stop Catodon first — a running instance holds open database connections that --clean will fail to drop:

sudo systemctl stop catodon   # or however you manage the process (pm2, etc.)

sudo -u postgres pg_restore -d catodon --clean --if-exists catodon-db-backup-<STAMP>.dump

--if-exists suppresses expected "does not exist, skipping" messages from --clean on a database that doesn't yet have everything the dump does — not an error.

Restore files and config to whatever path you backed them up from (the mediaDirectory path from step 2 above, and wherever .config lives):

sudo rm -rf /var/lib/catodon && sudo cp -a "/var/lib/catodon.backup-<STAMP>" /var/lib/catodon
rm -rf .config && cp -a ".config.backup-<STAMP>" .config

sudo systemctl start catodon

Notes

  • Object storage users: the files backup step is unnecessary — your media lives with your S3-compatible provider, which should have its own backup/ versioning story. You still need the database and config backups.
  • Valkey/Redis is not included here, because a Catodon upgrade does not touch that store: it keeps running across the upgrade and nothing in this procedure discards it. Do not read that as "it holds nothing worth keeping". It holds the notification history and read state of every account, which exists nowhere else: there is no notification table, and NotificationService keeps each account's notifications in a stream per user. It also holds pending federation deliveries and, if you enabled reactions buffering, unbaked reaction deltas. Anything that does discard the store, migrating between Redis and Valkey among them, loses all of it, so see Upgrading your in-memory store from Redis to Valkey, which has its own drain-and-backup steps.
  • Meilisearch data (if you enabled the optional meilisearch service) is a search index that rebuilds from Postgres, so it is not something you need to back up here.
  • If a dump or restore command fails, check the obvious things first: disk space, that the database/user names in the command actually match .config/default.yml, and (Docker) that docker compose exec can reach the db service at all (docker compose ps).
  • Keep the backup until you've confirmed the upgraded instance is healthy — don't delete it as same-day cleanup.