Skip to content

docs: Add guide for migrating a self-hosted database to Cloud - #853

Open
Zfinix wants to merge 2 commits into
mainfrom
chore/migrate-self-hosted-database
Open

Zfinix wants to merge 2 commits into
mainfrom
chore/migrate-self-hosted-database

Conversation

@Zfinix

@Zfinix Zfinix commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Adds a guide for moving a self-hosted database (Serverpod and PostgreSQL in Docker, for example on a VPS) into a Cloud project. Nothing covers this today, and the obvious pg_dump and pg_restore --data-only route fails on the tables Cloud writes itself.

  • The dump is taken with --data-only at the source, which orders tables by foreign key. A full dump restored with --data-only fails on serverpod_auth_core_profile.
  • --exclude-table-data covers the data Cloud writes on each deploy, each pattern keeping a trailing * so the table's ID sequence is left out too.
  • emailSecretHashPepper and the JWT keys are copied before any user rows land, otherwise sign-in fails with invalidCredentials and the first refresh deletes the session.
  • Profile images take a two-pass restore, since serverpod_auth_core_profile and serverpod_auth_core_profile_image reference each other and the Cloud database user cannot disable triggers.
  • Four troubleshooting entries for the errors a missed step produces.

Every step run against a Cloud project on the starter plan, with a Docker Compose source on PostgreSQL 16, at Serverpod 4.0.0 and Cloud CLI 1.0.0.

image

@Zfinix Zfinix self-assigned this Sep 21, 2026
@SandPod
SandPod requested a review from Isakdl September 21, 2026 07:32

The commands below use example names. Your database runs in a Docker Compose service called `postgres`, and your server runs in a service called `server`. The database is called `my_project`. Replace these names with your own.

Run the `docker compose` commands on your server, and the `serverpod cloud` commands from your server directory on your machine.

@developerjamiu developerjamiu Sep 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: "your server directory" reads as the VPS, which the sentence just called "your server".

Suggested change
Run the `docker compose` commands on your server, and the `serverpod cloud` commands from your server directory on your machine.
Run the `docker compose` commands on your server, and the `serverpod cloud` commands from your project's `<project>_server` folder on your own machine.

serverpod cloud db user create migrator
```

The `migrator` user can read and write rows. It can't change the schema, disable triggers, or turn off foreign key checks. That's why the dump contains data only. See [Access the database directly](/cloud/concepts/database#access-the-database-directly) for more about database users.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: "can't change the schema" is broader than the restore needs. The two things that matter here are triggers and foreign key checks.

Suggested change
The `migrator` user can read and write rows. It can't change the schema, disable triggers, or turn off foreign key checks. That's why the dump contains data only. See [Access the database directly](/cloud/concepts/database#access-the-database-directly) for more about database users.
The `migrator` user can read and write rows, but it can't disable triggers or turn off foreign key checks. That's why the dump contains data only. See [Access the database directly](/cloud/concepts/database#access-the-database-directly) for more about database users.

-c "SELECT module, version FROM serverpod_migrations ORDER BY module;"
```

Replace `<host>` and `<database>` with the values from `serverpod cloud db connection`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: serverpod cloud db connection prints a port too, and includes it in the connection string it prints.

Suggested change
Replace `<host>` and `<database>` with the values from `serverpod cloud db connection`.
Replace `<host>` and `<database>` with the values from `serverpod cloud db connection`. If it prints a port, add it after the host as `<host>:<port>`.

- **Keep the `*` at the end of each pattern.** It also leaves out each table's ID sequence. Without it, the dump carries your server's sequence values, and the restore resets Cloud's counters for those tables.
- **Everything else is included.** That covers your own tables, users, sessions, future calls, and files stored in the database.

`pg_dump` warns about circular foreign keys between `serverpod_auth_core_profile` and `serverpod_auth_core_profile_image`, with a hint to use a full dump. Ignore the hint. The warning only matters if some of your users have profile images. Count them:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: don't open a sentence with a code span, it reads as a symbol rather than a sentence. Lines 115 and 176 too.

Suggested change
`pg_dump` warns about circular foreign keys between `serverpod_auth_core_profile` and `serverpod_auth_core_profile_image`, with a hint to use a full dump. Ignore the hint. The warning only matters if some of your users have profile images. Count them:
The `pg_dump` command warns about circular foreign keys between `serverpod_auth_core_profile` and `serverpod_auth_core_profile_image`, with a hint to use a full dump. Ignore the hint. The warning only matters if some of your users have profile images. Count them:

---
sidebar_position: 5
sidebar_label: Migrate a self-hosted database
description: Database migration from a self-hosted Serverpod server to Serverpod Cloud. Copy your data, users, and auth secrets with pg_dump and pg_restore.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: "Database migration" is what Serverpod calls schema migrations, and the page checks "migration versions" a few lines later. Same for "the migration user" on line 204, which is the migrator user. Your call on the title.

Suggested change
description: Database migration from a self-hosted Serverpod server to Serverpod Cloud. Copy your data, users, and auth secrets with pg_dump and pg_restore.
description: Moving a self-hosted database into Serverpod Cloud with pg_dump and pg_restore, so your data, users, and sessions come across intact.


Three details in this command matter:

- **Dump with `--data-only`.** A data-only dump orders tables by their foreign keys, so users are restored before their profiles. A full dump doesn't, so restoring it can fail on foreign key errors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: reads as if any restore of a full dump fails. It's a full dump restored with --data-only that does, as the PR description says.

Suggested change
- **Dump with `--data-only`.** A data-only dump orders tables by their foreign keys, so users are restored before their profiles. A full dump doesn't, so restoring it can fail on foreign key errors.
- **Dump with `--data-only`.** A data-only dump orders tables by their foreign keys, so users are restored before their profiles. A full dump doesn't, so restoring it with `--data-only` can fail on foreign key errors.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants