Backup and Restore

Back up self-hosted AFFiNE before upgrades, storage moves, host migrations, and any operation that can affect persistent data.

A complete backup includes:

DataWhat it contains
PostgresUsers, workspaces, documents, permissions, and server metadata
Uploaded filesImages, attachments, and other blobs when filesystem storage is used
Configurationconfig/config.json, the Compose file, and .env for legacy deployments

Postgres backup

The database role and database name are not always affine. Legacy Compose deployments use DB_USERNAME and DB_DATABASE from .env. Do not copy a command that hardcodes affine unless those are the effective values in the running container.

The following command reads POSTGRES_USER and POSTGRES_DB from the running Postgres container, so it works with both the current defaults and legacy custom values:

docker exec affine_postgres sh -c   'pg_dump --format=custom --file=/tmp/affine.backup --username="$POSTGRES_USER" "$POSTGRES_DB"'

docker cp affine_postgres:/tmp/affine.backup ./affine.backup

Verify that the archive can be read:

docker exec affine_postgres sh -c   'pg_restore --list /tmp/affine.backup >/dev/null'

Move affine.backup to durable storage outside the AFFiNE deployment directory. Record the Compose file and effective database settings used to create it.

If the command reports role "affine" does not exist, the existing database was initialized with another role. Return to the Compose file that successfully starts the existing deployment and run the command above. Do not create an affine role merely to hide the error; first confirm that the container is using the expected Postgres data directory.

Postgres restore

Stop AFFiNE before restoring. Never restore over the only copy of a database, and never delete the old Postgres directory until the restored deployment has been verified.

The safest migration is to restore into a new, empty target Postgres data directory while retaining the original deployment and backup.

  1. Stop the AFFiNE services.

  2. Keep the old Postgres directory unchanged.

  3. Configure the target Compose deployment with the intended data path, database user, and database name.

  4. Start only the target Postgres service.

  5. Restore the archive with the target container's effective role and database.

  6. Start AFFiNE so that affine_migration can run.

  7. Verify users, workspaces, documents, uploads, and admin access before removing old data.

For a fresh target database:

docker compose up -d postgres
docker cp ./affine.backup affine_postgres:/tmp/affine.backup

docker exec affine_postgres sh -c   'pg_restore --format=custom --clean --if-exists --no-owner --no-privileges     --dbname="$POSTGRES_DB" --username="$POSTGRES_USER" /tmp/affine.backup'

docker compose up -d

Warnings about objects that do not yet exist can occur with --clean --if-exists. Any authentication error, missing role, or failed restore must be resolved before starting AFFiNE.

When moving a legacy .env deployment to the current layout, use the working legacy Compose file to create the backup, then restore into the fresh ./data/postgres target. See ​Upgrade and ​Legacy .env deployments.

Uploaded files

If filesystem storage is used, back up the entire mounted storage directory:

  • Current Compose default: ./data/storage

  • Legacy Compose: the effective UPLOAD_LOCATION value from .env

Keep this backup together with the matching Postgres backup. A database-only restore does not restore uploaded file contents.

If an S3-compatible provider is used, follow the storage provider's backup and versioning procedure.

Configuration

Keep copies of:

  • config/config.json or the effective CONFIG_LOCATION

  • docker-compose.yml

  • .env for legacy deployments

  • Any reverse-proxy, object-storage, mail, OAuth, or secret configuration required by the deployment

Modified server settings can also be exported from the Admin Panel.