diff --git a/apps/blog/content/blog/from-local-to-production-with-prisma-postgres/index.mdx b/apps/blog/content/blog/from-local-to-production-with-prisma-postgres/index.mdx index 04302e164dc..abcb6a7e08f 100644 --- a/apps/blog/content/blog/from-local-to-production-with-prisma-postgres/index.mdx +++ b/apps/blog/content/blog/from-local-to-production-with-prisma-postgres/index.mdx @@ -1,11 +1,12 @@ --- -title: "From Local Development to Production with Prisma Postgres" +title: "Migrate a local Postgres database to managed Postgres (2026): Prisma Postgres, Neon, Supabase, Railway and Render compared" slug: "from-local-to-production-with-prisma-postgres" date: "2026-07-31" +updatedAt: "2026-10-06" authors: - "Søren Bramer Schmidt" -metaTitle: "From Local Development to Production with Prisma Postgres" -metaDescription: "The exact checklist for moving a Prisma app from local Postgres, Docker or prisma dev, to hosted Prisma Postgres: schema, data, config, and validation." +metaTitle: "Migrate a local Postgres database to managed Postgres (2026)" +metaDescription: "Prisma Postgres takes a laptop Postgres database in three commands. Neon, Supabase, Railway and Render compared on import tool, free tier and Postgres version." heroImagePath: "/from-local-to-production-with-prisma-postgres/imgs/hero.svg" heroImageAlt: "A .env file swaps a local Postgres connection string for a hosted Prisma Postgres one, fanning out to local and production targets" metaImagePath: "/from-local-to-production-with-prisma-postgres/imgs/meta.png" @@ -14,13 +15,51 @@ tags: - "education" --- -Prototyping against a local Postgres is the easy part; the awkward step is moving the working app to a hosted database. This guide is the checklist for that move: take a Prisma project running on Docker Postgres or a local `prisma dev` instance, and land it on [Prisma Postgres](https://www.prisma.io/docs/postgres), a managed PostgreSQL database, with the schema, data, and migration history intact. The whole transition is five steps, and the only thing that changes for your application is where its connection strings point. +[Prisma Postgres](https://www.prisma.io/docs/postgres), a managed PostgreSQL database, takes the Postgres database on your laptop in three commands, `npx prisma postgres create`, `pg_dump` and `pg_restore`, on a Free plan with no credit card, PostgreSQL 17 and connection pooling included, and every Postgres client you use today keeps working. Neon is the better pick if you need point-in-time recovery or a branch that copies production data, and Supabase if you want auth and file storage from the same vendor. Prisma makes Prisma ORM, Prisma Postgres and Prisma Compute. -## The local-to-production gap +Every managed Postgres service accepts a standard `pg_dump` archive; what differs is how you create the target, which PostgreSQL version it runs and what the free tier allows, and the [table below](#how-the-import-works) compares five services on those. The rest of the post is the checklist for a Prisma project: five steps that take an app on Docker Postgres or a local `prisma dev` instance to Prisma Postgres with schema, data and migration history intact. + +## Quick recommendation + +If the database runs PostgreSQL 17 or older, dump it and restore it into Prisma Postgres on the Free plan; the docs page [Migrate a local Postgres database to a managed Postgres service](https://www.prisma.io/docs/postgres/import-from-existing-database) has the three commands and the checks to run first. If your laptop already runs PostgreSQL 18, the PostgreSQL 17 tools cannot dump it: read the caveat in the [import guide](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql) first, or pick Neon or Render, which offer PostgreSQL 18. If you want the app hosted next to the database, [Prisma Compute](https://www.prisma.io/docs/compute) runs it on the same plan. + +| Your situation | Pick | Why | +| --- | --- | --- | +| A laptop database on PostgreSQL 17 or older, standard tools, nothing else to move | Prisma Postgres | Three commands, Free plan with no credit card, PostgreSQL 17, connection pooling included, and any Postgres client keeps working | +| A Prisma ORM project whose migration history has to come along | Prisma Postgres | The `_prisma_migrations` table travels in the dump, and `npx prisma@7 migrate status` confirms it arrived | +| The app should run next to the database, on one plan | Prisma Compute + Prisma Postgres | Add a Composer declaration, run your build and deploy with `npx prisma deploy module.ts`; Composer needs Node.js 22.18 or newer ([prerequisites](https://www.prisma.io/docs/prisma-compute/deploy#prerequisites)), and the Free plan covers app and database with no usage billing | +| A rehearsal of the restore with throwaway data, no account yet | `npx create-db@latest` | A temporary Prisma Postgres database, deleted after 24 hours unless you claim it; never for the real copy | +| You need point-in-time recovery or a branch that copies production data | Neon | Its plans carry a restore window (6 hours on Free, up to 7 days on Launch, up to 30 days on Scale) and 10 branches per project on Free ([pricing](https://neon.com/pricing), as of October 2026); Prisma Postgres has daily backups on paid plans and no point-in-time recovery yet | +| You want auth and file storage from the database vendor | Supabase | The Free plan includes Auth for 50,000 monthly active users and 1 GB of file storage ([pricing](https://supabase.com/pricing), as of October 2026) | +| The source already runs PostgreSQL 18 | Neon or Render | Both offer PostgreSQL 18 databases ([Neon](https://neon.com/docs/postgresql/postgres-version-policy), [Render](https://render.com/docs/postgresql-creating-connecting), as of October 2026); the PostgreSQL 17 tools Prisma Postgres needs cannot dump an 18 server | + +## How the import works + +:::note[How we compared] + +We make Prisma Postgres and list it first for the situations in the table above. The other four rows rest on each vendor's own documentation as read on 2026-10-05, linked in the last column, on four criteria: the import tool, the steps between an empty account and a restored database (our count, following each vendor's guide), the free tier and the PostgreSQL versions offered. The one thing Neon does that Prisma Postgres does not: a restore window on every plan and branches that copy production data. + +::: + +| Service | Import tool | Steps | Free tier | PostgreSQL version | Source (as of October 2026) | +| --- | --- | --- | --- | --- | --- | +| **Prisma Postgres** | `pg_dump` and `pg_restore` with PostgreSQL 17 tools, over the direct connection | 3: `npx prisma postgres create`, `pg_dump -Fc`, `pg_restore --no-owner --no-privileges` | 200k operations a month, 500 MB, 50 databases, no credit card | 17 | [Import guide](https://www.prisma.io/docs/postgres/import-from-existing-database), [pricing](https://www.prisma.io/pricing) | +| **Neon** | Import Data Assistant: paste the source connection string, for databases under 10 GB on Postgres 14 to 17; over 10 GB, `pg_dump` and `pg_restore` | 4: open the assistant, paste the source connection string and run its checks, create the project, start the import (one-hour limit) | 100 CU-hours and 1 GB per project (20 GB across the account), 100 projects | 14, 15, 16, 17 or 18 | [Import Data Assistant](https://neon.com/docs/import/import-data-assistant), [versions](https://neon.com/docs/postgresql/postgres-version-policy), [pricing](https://neon.com/pricing) | +| **Supabase** | `pg_dump` with `--no-owner --no-privileges`, then `pg_restore` through the session pooler; roles and the row-level security status of tables are not migrated | 5: set up a migration machine, prepare the project, dump, restore, then recreate roles and re-enable RLS | 2 active projects, 500 MB each, paused after a week without activity | 15 or 17 | [Migration guide](https://supabase.com/docs/guides/platform/migrating-to-supabase/postgres), [pricing](https://supabase.com/pricing), [supabase/postgres](https://github.com/supabase/postgres) | +| **Railway** | Standard `pg_dump` and `pg_restore` over the database's public TCP proxy (`DATABASE_PUBLIC_URL`) | 4: add Postgres, enable public access, dump, restore | Free plan with $1 of monthly usage credit; Hobby is $5 a month with $5 of usage included | 16 on the `latest` tag of Railway's `postgres-ssl` image, with other major versions as tags | [Postgres guide](https://docs.railway.com/guides/postgresql), [template](https://github.com/railwayapp-templates/postgres-ssl), [pricing](https://railway.com/pricing) | +| **Render** | Standard `pg_dump` and `pg_restore` against the external database URL | 3: create a database, dump, restore | One free database per workspace, 1 GB, expires 30 days after creation | 13 to 18 | [Creating and connecting](https://render.com/docs/postgresql-creating-connecting), [backups and restores](https://render.com/docs/postgresql-backups), [free tier](https://render.com/docs/free) | -Most Prisma projects start the same way: a schema, a local database, and a tight iteration loop. The trouble starts when the app is ready for other people. Production needs a database that is not your laptop, and most tutorials end right before the part where the data has to live somewhere real. +A `pg_dump` archive does not carry PostgreSQL roles, so on the four `pg_dump` paths the ownership and grant statements have to be skipped or rewritten; Supabase says so outright, and Neon's assistant does not document what it does with roles. The free tiers differ in what runs out: Render's free database expires after 30 days, Supabase pauses a free project after a week without activity, Railway's free plan is a $1 monthly usage credit, Neon's is 100 CU-hours per project, and Prisma Postgres counts operations, 200k a month, with no expiry and no credit card. -The move is smaller than it looks. Your schema does not change. Your queries do not change. Prisma Client does not change. Three things move: where the database runs, the connection strings that point at it, and the schema and data that have to arrive there. As a checklist: +## Where Prisma Postgres is the wrong choice + +- You need point-in-time recovery or a branch that copies production data. Prisma Postgres takes daily backups on paid plans, kept 7 days on Starter and Pro and 30 days on Business, and has no point-in-time recovery yet; Neon's plans carry a restore window and branches. +- The source runs PostgreSQL 18. The PostgreSQL 17 tools that Prisma Postgres needs cannot dump it, and the [import guide](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql) says a source on 18 needs a separate downgrade-compatible process; Neon and Render offer PostgreSQL 18 databases. +- The database uses PostGIS or TimescaleDB. Neither is on the [supported extensions list](https://www.prisma.io/docs/postgres/database/postgres-extensions#supported-extensions). + +## The local-to-production gap + +Three things move: where the database runs, the connection strings that point at it, and the schema and data that have to arrive there. The schema, the queries and Prisma Client stay as they are. As a checklist: 1. Develop against a local database, with `prisma dev` or Docker. 2. Create the hosted Prisma Postgres database. @@ -28,41 +67,19 @@ The move is smaller than it looks. Your schema does not change. Your queries do 4. Replay the schema, and move the data if it matters. 5. Validate, deploy, and keep the local loop. -The rest of this post walks through each step with the exact commands. +The commands below pin `prisma@7`, because the post follows the Prisma ORM 7 workflow (`migrate dev`, `prisma.config.ts`); Prisma ORM 8, the version npm installs by default, renames or drops them, and [Coming from Prisma ORM 7](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7) maps each one. ## Start local: `prisma dev` or Docker -The classic local setup is [Docker](https://www.docker.com). You write a compose file, pick a port, mount a volume so data survives restarts, and start the container: - -```yaml -# docker-compose.yml -services: - postgres: - image: postgres:17 - ports: - - "5432:5432" - environment: - POSTGRES_PASSWORD: postgres - volumes: - - pgdata:/var/lib/postgresql/data -volumes: - pgdata: -``` - -```text -# .env -DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" -``` - -This works, and if you already have it, keep it: the rest of the checklist is identical. But it carries baggage that has nothing to do with your app. A container runtime has to be installed and running (Docker Desktop, OrbStack, Colima, or another). The compose file has to be maintained. Port 5432 collides the moment a second project does the same thing. And the whole arrangement exists to run one process. +The classic local setup is [Docker](https://www.docker.com): a `postgres:17` container on port 5432, a volume so data survives restarts, and `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"` in `.env`. If you already have it, keep it: the rest of the checklist is identical. The lighter route is `prisma dev`, which ships with the Prisma CLI: ```bash -npx prisma dev +npx prisma@7 dev ``` -That one command starts a local Prisma Postgres instance, powered by [PGlite](https://pglite.dev), an embedded Postgres engine. There is no daemon to install, no image to pull, and no container to manage. When the server is up, it prints connection strings ready to paste into `.env`: +That starts a local Prisma Postgres instance on [PGlite](https://pglite.dev), an embedded Postgres engine, with no daemon or container, and prints connection strings ready to paste into `.env`: ```text ✔ Your local Prisma Postgres server default is now running @@ -72,24 +89,17 @@ That one command starts a local Prisma Postgres instance, powered by [PGlite](ht DATABASE_URL="postgres://postgres:postgres@localhost:51214/template1?sslmode=disable&..." ``` -Compared to the Docker setup, `prisma dev` is easier on four counts: +The only requirement is a Node.js version Prisma ORM 7 supports: 20.19 or later, 22.12 or later, or 24; if you plan to deploy on Prisma Compute as well, use 22.18 or newer, because Composer needs it. `npx prisma@7 dev --name myapp` gives every project its own named instance, `--detach` runs the server in the background, and `prisma dev ls`, `prisma dev stop ` and `prisma dev rm ` list, stop and delete instances. -- **No extra infrastructure.** No Docker daemon, no compose file, no volumes, no port mapping. Node.js 20 or later is the only requirement. -- **Per-project instances.** `npx prisma dev --name myapp` gives every project its own named database with a stable connection string, so nothing collides. -- **A built-in lifecycle.** `npx prisma dev --detach` runs the server in the background, `prisma dev ls` lists your instances, and `prisma dev stop ` and `prisma dev rm ` shut down and delete them. No orphaned containers, no stale volumes. -- **The same tooling in both places.** Local `prisma dev` and hosted Prisma Postgres take the same connection-string interface and the same CLI, so what you learn locally is what you run against production. +It is a development tool that serves one connection at a time, not something you self-host; the [local development docs](https://www.prisma.io/docs/local-development/postgres) have the full reference. -It speaks standard Postgres on the wire, so `psql`, GUIs, and standard Postgres drivers connect to it. The trade-offs: the local server accepts one connection at a time, so a second client waits for the first to let go rather than failing outright, and it is a development tool, ideal for iteration and tests rather than something you self-host. The [local development docs](https://www.prisma.io/docs/local-development/postgres) cover the full instance-management reference. - -Whichever route you chose, finish the local loop by creating the schema and, if your config defines a seed script, seeding: +Whichever route you chose, create the schema and, if your config defines a seed script, seed: ```bash -npx prisma migrate dev -npx prisma db seed +npx prisma@7 migrate dev +npx prisma@7 db seed ``` -From here, both starting points follow the same checklist. - ## Create the hosted database Create the production database in the [Prisma Console](https://console.prisma.io): @@ -98,18 +108,18 @@ Create the production database in the [Prisma Console](https://console.prisma.io 2. Name the project, click **Get started** under Prisma Postgres, and pick the region closest to your users. 3. Click **Create project**, and wait for the database to finish provisioning. -Then grab the connection strings you will need: +Then copy the connection strings: 1. Select the database and click **Connect to your database**. 2. Click **Generate new connection string** and copy both strings it gives you. -Every Prisma Postgres database has [two connection strings](https://www.prisma.io/docs/postgres/database/connecting-to-your-database): a **pooled** one (hostname `pooled.db.prisma.io`) for application traffic, and a **direct** one (`db.prisma.io`) for migrations and standard Postgres tools like `pg_restore`. You will use both in the next steps. +Every Prisma Postgres database has [two connection strings](https://www.prisma.io/docs/postgres/database/connecting-to-your-database): **pooled** (`pooled.db.prisma.io`) for application traffic and **direct** (`db.prisma.io`) for migrations and tools like `pg_restore`. You will use both. -Two CLI alternatives, depending on where you are: for a brand-new project, `npx prisma@7.9.1 init --db` scaffolds the schema and provisions a Prisma Postgres database in one go, and [`npx create-db@latest`](https://www.prisma.io/docs/postgres/npx-create-db) creates a temporary database with no sign-up; claim it within 24 hours to keep it, or it is deleted. +Two CLI alternatives: for a brand-new project, `npx prisma@7.9.1 init --db` scaffolds the schema and provisions a Prisma Postgres database in one go, and [`npx create-db@latest`](https://www.prisma.io/docs/postgres/npx-create-db) creates a temporary database with no sign-up, deleted after 24 hours unless you claim it. ## Update env and config -Two things before you touch `.env`. Check that it is gitignored, because production credentials are about to live in it. And keep your local URLs somewhere you can get them back, because the steps after this one need them twice; for a `prisma dev` instance, `npx prisma dev ls` shows the connection string again. +Before you touch `.env`, check that it is gitignored, because production credentials are about to live in it, and keep your local URLs somewhere you can get them back; for a `prisma dev` instance, `npx prisma@7 dev ls` shows the connection string again. Now point the app at the hosted database by swapping the connection strings: @@ -122,9 +132,7 @@ DATABASE_URL="postgres://USER:PASSWORD@pooled.db.prisma.io:5432/postgres?sslmode DIRECT_URL="postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require" ``` -The pooled string is what the app reads through `DATABASE_URL` at runtime. The direct string is for the Prisma CLI. The pooler runs PgBouncer in transaction mode: it lends a backend connection for the length of each transaction, then takes it back. Ordinary application queries do not notice, which is why your app code stays the same. Work that spans transactions does: session-level `SET`, `LISTEN`/`NOTIFY`, and queries running longer than ten minutes want the direct string. Migrations want it too, because they hold advisory locks and session state across statements, and fail through the pooler. - -So each side gets its own variable: the CLI on the direct string, application traffic on the pooled one. Running the app on the direct string instead quietly exhausts connections under load. Add `DIRECT_URL` and point the datasource in `prisma.config.ts` at it: +The app reads the pooled string through `DATABASE_URL` at runtime; the direct string is for the Prisma CLI, because migrations hold locks and session state across statements and fail through the PgBouncer transaction pooler, while an app on the direct string quietly exhausts connections under load. [Connecting to your database](https://www.prisma.io/docs/postgres/database/connecting-to-your-database) lists the other work that wants the direct string. Add `DIRECT_URL` and point the datasource in `prisma.config.ts` at it: ```ts // prisma.config.ts @@ -135,41 +143,39 @@ export default defineConfig({ schema: "prisma/schema.prisma", datasource: { url: env("DIRECT_URL"), + // Local development with `prisma dev` only; unset in production. + shadowDatabaseUrl: process.env["SHADOW_DATABASE_URL"], }, }); ``` -The `url` line is the only change; keep whatever else your config already declares. That snippet is the Prisma ORM 7 setup, where connection config lives in `prisma.config.ts`. On Prisma ORM 6 the same job is done by `directUrl` in the schema's `datasource` block. Prisma ORM 7 moves connection URLs out of the schema entirely, and the direct string goes in the config's `url`, as above. For local development, set `DIRECT_URL` to the same value as `DATABASE_URL`; the local server has no pooler, so the two are one URL. +The `url` line is the change production needs. That is the Prisma ORM 7 setup, where connection URLs live in `prisma.config.ts`; on Prisma ORM 6 the same job is done by `directUrl` in the schema's `datasource` block. For local development with `prisma dev`, set `DIRECT_URL` to the same value as `DATABASE_URL`, because the local server has no pooler, and copy the `SHADOW_DATABASE_URL` it prints into `.env` as well. Prisma Migrate uses that second database as its shadow database, and without it a later `migrate dev` fails with `relation "..." already exists`. `migrate deploy` in production does not use a shadow database, so the variable stays unset there. -That is the entire change: two environment values and one config line. No query rewrites, no driver swap, no schema edits. The app reads `DATABASE_URL` from the environment at runtime, and the CLI reads `prisma.config.ts` on every run. - -Leave `.env` pointing at production for now: every Prisma CLI command in the rest of this checklist runs against the hosted database, and the last step switches local development back. Treat that window carefully. Anything that loads `.env` now talks to production, including your test suite and any seed script that starts by clearing tables, and `migrate dev` and `migrate reset` will do to production exactly what their names say. If a command in this window is not on the checklist, check which database it will hit before you run it. The permanent home for these credentials is your hosting platform's environment settings, not the file. +Leave `.env` pointing at production for now: every Prisma CLI command in the rest of this checklist runs against the hosted database, and the last step switches local development back. Anything that loads `.env` in this window talks to production, including your test suite and any seed script that clears tables, and `migrate dev` and `migrate reset` will do to production exactly what their names say; the permanent home for these credentials is your hosting platform's environment settings. ## Move the schema and the data -There are two situations, and they need different commands. - **Starting fresh in production.** If your local data is disposable prototype data, replay the migration history against the hosted database and reseed. -This assumes you have a `prisma/migrations` folder. If you prototyped with `npx prisma db push`, you do not: `db push` never writes migration files, and `migrate dev` will not create them retroactively against a database that already has your tables, it stops with `P3005, The database schema is not empty`. Baseline instead, with `.env` pointing at your local database: +This assumes a `prisma/migrations` folder, which you do not have if you prototyped with `npx prisma@7 db push`: `db push` never writes migration files, and `migrate dev` stops with `P3005, The database schema is not empty` against a database that already has your tables. Baseline instead, with `.env` pointing at your local database: ```bash mkdir -p prisma/migrations/0_init -npx prisma migrate diff \ +npx prisma@7 migrate diff \ --from-empty \ --to-schema prisma/schema.prisma \ --script > prisma/migrations/0_init/migration.sql -npx prisma migrate resolve --applied 0_init +npx prisma@7 migrate resolve --applied 0_init ``` -That writes the migration your schema implies and records it as already applied locally, without touching your data. Point `.env` back at production before continuing. The [baselining docs](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/baselining) cover the workflow in full. Do this before dumping, too, if you are taking the data path below: it gives the dump a migration history to carry. +That writes the migration your schema implies and records it as applied locally, without touching your data; the [baselining docs](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/baselining) cover the workflow in full. Point `.env` back at production before continuing. Do this before dumping, too, if you take the data path below, so the dump has a migration history to carry. With `.env` on production, the CLI applies your migrations to the hosted database, over the direct connection: ```bash -npx prisma migrate deploy +npx prisma@7 migrate deploy ``` ```text @@ -178,80 +184,89 @@ npx prisma migrate deploy All migrations have been successfully applied. ``` -`migrate deploy` is the production counterpart of `migrate dev`: it applies the committed migration files in order, with no schema drafting, no prompts, and no drift detection. That makes it safe to put in CI or your deploy pipeline, where it belongs. The [development and production workflows docs](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/development-and-production) explain the split. If you have a seed script for production defaults, run it once now, while `.env` points at production. +`migrate deploy` applies the committed migration files in order, with no schema drafting, no prompts and no drift detection, which makes it safe for CI; the [workflows docs](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/development-and-production) explain the split from `migrate dev`. If you have a seed script for production defaults, run it once now, while `.env` points at production. -**Bringing the data along.** If the local data matters, copy it with the standard Postgres tools. The dump and restore below both take their database on the command line with `-d`, so neither reads `.env`; it does not matter that `.env` currently points at production. +**Bringing the data along.** If the local data matters, copy it with the standard Postgres tools. Both commands below take their database on the command line with `-d`, so neither reads `.env`. -This is the Docker path. If your data lives in a local `prisma dev` instance instead, use the [Prisma VS Code extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma), whose push-to-cloud action moves a local database to a remote Prisma Postgres instance without any dump file. A local instance is PGlite rather than a full Postgres server, and dumping one is not the well-trodden path: it serves a single connection, so anything else holding it blocks the dump, and `pg_dump` rejects Prisma's pool-tuning parameters unless you trim the printed URL back to `?sslmode=disable`. +The commands below are the Docker path. If your data lives in a local `prisma dev` instance, use the [Prisma VS Code extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma), whose push-to-cloud action moves a local database to Prisma Postgres without a dump file; a PGlite instance serves a single connection, so anything else holding it blocks a dump, and `pg_dump` rejects Prisma's pool-tuning parameters unless you trim the printed URL back to `?sslmode=disable`. Export from the Docker database: ```bash -pg_dump -Fc -v -n public -f db_dump.bak \ +pg_dump -Fc -v -n public --large-objects -f db_dump.bak \ -d "postgresql://postgres:postgres@localhost:5432/postgres" ``` -Import into Prisma Postgres using the direct connection string. `--no-owner` and `--no-acl` skip the ownership and grant statements that name your local roles, which do not exist in Prisma Postgres: +Import into Prisma Postgres using the direct connection string. `--no-owner` and `--no-privileges` skip the ownership and grant statements that name your local roles, which do not exist in Prisma Postgres: ```bash pg_restore \ --no-owner \ - --no-acl \ + --no-privileges \ -d "postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require" \ -v \ ./db_dump.bak ``` -Three details worth knowing. First, Prisma Postgres runs PostgreSQL 17, so your `pg_dump` and `pg_restore` need to be version 17; check with `pg_dump --version`, and install the matching client tools if they are older. Second, `-n public` dumps the `public` schema, which is where Prisma puts your tables by default, including the `_prisma_migrations` table. Your migration history travels with the data, so you do not replay it separately; the `migrate status` check below is how you confirm it arrived. If you use `multiSchema` or keep tables outside `public`, add a `-n` flag per schema, or the restore will look clean while those tables are missing. Third, a restore prints a `schema "public" already exists` warning, which is safe to ignore. Take the two branches above as mutually exclusive: restoring on top of a database you already ran `migrate deploy` against produces genuine `already exists` errors, and that is a different situation. The [PostgreSQL import guide](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql) covers this flow end to end, including provider-specific connection notes. +`--no-owner` and `--no-privileges` leave row-level security policies in place. A policy that names a local role fails on restore, and `pg_restore` moves on to the next object, so list the policies before you dump with the query in the full guide's [inspect step](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql#1-inspect-the-source-database). Mark the ones that name local roles with `;` in the `pg_restore --list` manifest, as its [export step](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql#3-stop-source-writes-and-export-the-final-snapshot) shows, and pass that manifest to `pg_restore` with `--use-list`, as its [restore step](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql#4-restore-into-prisma-postgres) does; the manifest does nothing without that flag. Recreate those policies for your Prisma Postgres roles once the data is in. + +Three details decide whether the restore lands cleanly. First, Prisma Postgres runs PostgreSQL 17, so `pg_dump` and `pg_restore` need to be version 17 (check with `pg_dump --version`); a PostgreSQL 17 `pg_dump` reads older servers but not an 18 one, as [noted above](#where-prisma-postgres-is-the-wrong-choice). Second, `-n public` dumps the `public` schema, where Prisma puts your tables by default, including `_prisma_migrations`, so your migration history travels with the data and the `migrate status` check below confirms it arrived; if you use `multiSchema` or keep tables outside `public`, add a `-n` flag per schema, or the restore looks clean while those tables are missing. `-n` also skips objects in other schemas that `public` depends on, so a `public` table that uses a type, function or sequence from another schema can fail to restore. Check for those dependencies before you dump; add a `-n` flag for each schema they live in, or drop `-n` and dump every schema, as the full guide's [export step](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql#3-stop-source-writes-and-export-the-final-snapshot) does. `-n` leaves large objects out of the dump as well, which is why the command carries `--large-objects`: without it, a table that stores files through PostgreSQL's large-object functions restores its rows but not the files. Third, the target database already has a `public` schema, so the restore reports `schema "public" already exists`. That is an error, not a warning: `pg_restore` continues past it by default and prints a count of errors at the end. Read every error above that count. On a fresh database the `public` schema error is the one to expect; treat any other error as something that did not load, and another `already exists` error in particular means you restored on top of a database you had already run `migrate deploy` against. Because `pg_restore` does not roll back what it already loaded, the fix is a new, empty database, not a second run. To stop at the first error and leave the target unchanged instead, follow the full guide's [restore step](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql#4-restore-into-prisma-postgres), which runs `pg_restore` with `--single-transaction --exit-on-error` and a restore manifest; with this dump, prefix only the `SCHEMA - public` and `COMMENT - SCHEMA public` lines with `;`, as the [Supabase guide](https://www.prisma.io/docs/guides/switch-to-prisma-postgres/from-supabase#5-restore-into-prisma-postgres) does. That keeps the `public` schema the target already has and still restores its tables, sequences and data. ## Validate, then deploy Do not declare victory on a successful restore. Three checks, then the switch: -1. **Migration status.** Run `npx prisma migrate status` against the hosted database. It compares your migration files against the `_prisma_migrations` table, so it confirms the history arrived intact, not that every column matches your schema: +1. **Migration status.** Run `npx prisma@7 migrate status` against the hosted database. It compares your migration files against the `_prisma_migrations` table, so it confirms the history arrived intact, not that every column matches your schema: ```text Database schema is up to date! ``` -2. **Row counts.** Spot-check that the data arrived. Open the **Studio** tab in the Console to browse the hosted data, or run a count against the direct connection: +2. **Row counts.** Open the **Studio** tab in the Console, or run a count against the direct connection: ```bash psql "postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require" \ -c 'SELECT count(*) FROM "User";' ``` -3. **Smoke test.** Start the app locally; `.env` still points at the hosted database. Click through the main flows. You are verifying config, not code, so the reads carry most of the signal. If you imported real data, keep the writes to records you are willing to clean up afterwards, or exercise them before the import instead: cascades, sequence values, and anything that sends mail do not come back with a `DELETE`. +3. **Smoke test.** Start the app locally, with `.env` still on the hosted database, and click through the main flows. You are verifying config, not code, so the reads carry most of the signal; if you imported real data, keep the writes to records you can clean up afterwards, because cascades, sequence values and anything that sends mail do not come back with a `DELETE`. -4. **Deploy.** Set `DATABASE_URL` and `DIRECT_URL` in the hosting platform, add `npx prisma migrate deploy` to the deploy pipeline so future migrations apply automatically, and ship. +4. **Deploy.** Set `DATABASE_URL` and `DIRECT_URL` in the hosting platform, add `npx prisma@7 migrate deploy` to the deploy pipeline so future migrations apply automatically, and ship. -Then put the local URLs back in `.env`, both `DATABASE_URL` and `DIRECT_URL`, and keep the loop you started with: iterate locally against `prisma dev` with `migrate dev`, commit the migration files, and let the pipeline replay them in production. Change both lines in the same edit. A leftover `DIRECT_URL` points the CLI at production while your app runs local, and the reverse, a leftover `DATABASE_URL`, is worse: the app reads and writes production while everything looks local, with no error to tip you off. Keep the old local database until production has run for a while, too. `db_dump.bak` is a file you can lose, and `docker compose down -v` or `npx prisma dev rm ` deletes the last live copy of that data. +Then put both local URLs back in `.env` in the same edit and keep the loop: `migrate dev` against `prisma dev` locally, commit the migration files, and let the pipeline replay them in production. A leftover `DIRECT_URL` points the CLI at production while your app runs local, and a leftover `DATABASE_URL` is worse: the app reads and writes production while everything looks local. Keep the old local database until production has run for a while, because `docker compose down -v` or `npx prisma@7 dev rm ` deletes the last live copy of that data. ## Frequently asked questions - - -Only if it matters. Prototype data is usually better left behind: run `npx prisma migrate deploy` against the hosted database to recreate the schema from your committed migration files, then reseed. Copy the data with `pg_dump` and `pg_restore` when it is real: content you authored, imported records, or anything users would miss. - - -No. The schema, the generated client, and the queries you wrote stay the same. The changes are two environment variables, the pooled `DATABASE_URL` and the direct `DIRECT_URL`, plus the one config line that points the Prisma CLI at the direct connection. That is what developing against local Prisma Postgres buys you: local and hosted take the same tooling behind the same environment variables. The one thing to check is workloads that hold session state across transactions, such as `LISTEN`/`NOTIFY` or queries running over ten minutes; in production those belong on the direct connection. - - -It travels with the data. Prisma Migrate records applied migrations in a `_prisma_migrations` table inside your database, in whichever schema your connection targets, `public` by default, and a dump of that schema includes it. After the restore, `npx prisma migrate status` reports the hosted database as up to date, and future `migrate deploy` runs continue from the same history. - - -Yes, that is the intended workflow. Production traffic hits hosted Prisma Postgres while daily development stays on a local `prisma dev` instance: fast and disposable. Schema changes flow one way, from `migrate dev` locally, through git, to `migrate deploy` in your pipeline. Named instances (`npx prisma dev --name myapp`) keep several projects from colliding. - - - -## Recap - -The checklist, condensed: - -1. **Develop locally.** `npx prisma dev` for a one-command local Prisma Postgres, or keep your Docker Postgres. Apply schema changes with `npx prisma migrate dev`. -2. **Create the hosted database.** Console **New project**, or `npx prisma@7.9.1 init --db` for a brand-new project, or `npx create-db@latest` and claim it within 24 hours. Copy the pooled and direct connection strings. -3. **Update config.** Set the pooled `DATABASE_URL` and the direct `DIRECT_URL`, point the config's datasource at `DIRECT_URL`, and store the production URLs in your platform's env settings. -4. **Move schema and data.** Fresh start: `npx prisma migrate deploy` plus your seed. With data: `pg_dump -Fc` locally, `pg_restore` over the direct connection, history included. -5. **Validate.** `npx prisma migrate status`, row counts in Prisma Studio, one smoke test, then deploy with `migrate deploy` in the pipeline. - -For your application code, the gap between local and production is a connection-string swap. Start local with `npx prisma dev`, and when the app is ready for other people, provision the hosted database in the [Console](https://console.prisma.io) and work the five steps above. +### Do I have to move my local data to production? + +Move it only if it matters. For prototype data, run `npx prisma@7 migrate deploy` against the hosted database to recreate the schema from your committed migration files, then reseed. Copy it when it is real: content you authored, imported records, or anything users would miss. + +### Does my application code change when I move to Prisma Postgres? + +No, the schema, the generated client and the queries you wrote stay the same. The changes are two environment variables, the pooled `DATABASE_URL` and the direct `DIRECT_URL`, plus the one config line that points the Prisma CLI at the direct connection. + +### Should I keep using prisma dev after production exists? + +Yes, that is the intended workflow: production traffic hits hosted Prisma Postgres while daily development stays on a local `prisma dev` instance, and schema changes flow one way, from `migrate dev` locally, through git, to `migrate deploy` in your pipeline. + +## Sources + +Checked on 2026-10-05: + +- Prisma: [Import from PostgreSQL](https://www.prisma.io/docs/prisma-postgres/import-from-existing-database-postgresql), [Connecting to your database](https://www.prisma.io/docs/postgres/database/connecting-to-your-database), [pricing](https://www.prisma.io/pricing) +- Neon: [Import Data Assistant](https://neon.com/docs/import/import-data-assistant), [Postgres version policy](https://neon.com/docs/postgresql/postgres-version-policy), [pricing](https://neon.com/pricing) +- Supabase: [Migrate to Supabase from Postgres](https://supabase.com/docs/guides/platform/migrating-to-supabase/postgres), [pricing](https://supabase.com/pricing), [supabase/postgres README](https://github.com/supabase/postgres) +- Railway: [PostgreSQL guide](https://docs.railway.com/guides/postgresql), [postgres-ssl template](https://github.com/railwayapp-templates/postgres-ssl), [pricing](https://railway.com/pricing) +- Render: [Creating and connecting to Postgres](https://render.com/docs/postgresql-creating-connecting), [Backups and exports](https://render.com/docs/postgresql-backups), [Free instances](https://render.com/docs/free) + +Checked on 2026-10-06: + +- Prisma: [Migrate from Supabase](https://www.prisma.io/docs/guides/switch-to-prisma-postgres/from-supabase) +- PostgreSQL 17: [pg_dump](https://www.postgresql.org/docs/17/app-pgdump.html), [pg_restore](https://www.postgresql.org/docs/17/app-pgrestore.html) + +## Update history + +- 2026-10-06: added `--large-objects` to the dump command, described the `public` schema restore error and how to read the error count `pg_restore` prints, named the two manifest lines to skip and the dependencies a `-n public` dump leaves out, and pointed to the full guide for row-level security policies that name local roles. +- 2026-10-05: added the Node.js 22.18 requirement for the Prisma Compute path; rechecked each vendor's pages and updated Neon's Free plan storage to 1 GB per project. +- 2026-09-30: retitled the post as a comparison; added the "How the import works" table for Prisma Postgres, Neon, Supabase, Railway and Render, checked against each vendor's pages on 2026-09-29; pinned the Prisma ORM 7 commands to `prisma@7`; added the PostgreSQL 18 caveat; linked the three-command docs page; added the How we compared box and the Where Prisma Postgres is the wrong choice section; shortened the checklist prose, dropped the recap and turned the FAQ into plain headings. +- 2026-07-31: first published. diff --git a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx index 609b3a1c7f0..04b03cf1738 100644 --- a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx +++ b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx @@ -126,6 +126,8 @@ Use the **direct** connection string with `psql`, `pg_dump`, `pg_restore`, and G psql "postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require" ``` +The same direct string is where `pg_restore` loads an existing database; see [Migrate a local Postgres database to Prisma Postgres](/postgres/import-from-existing-database) for the three commands. + For non-Prisma ORMs, see the getting-started guides for [Drizzle](/prisma-postgres/quickstart/drizzle-orm), [Kysely](/prisma-postgres/quickstart/kysely), or [TypeORM](/prisma-postgres/quickstart/typeorm). ## Troubleshooting diff --git a/apps/docs/content/docs/postgres/import-from-existing-database.mdx b/apps/docs/content/docs/postgres/import-from-existing-database.mdx index ce079a49251..57558557d2e 100644 --- a/apps/docs/content/docs/postgres/import-from-existing-database.mdx +++ b/apps/docs/content/docs/postgres/import-from-existing-database.mdx @@ -1,27 +1,82 @@ --- -title: Import from existing database -description: Choose the right path to import data from PostgreSQL or MySQL into Prisma Postgres. +title: Migrate a local Postgres database to a managed Postgres service (Prisma Postgres) +description: Copy a PostgreSQL database from your laptop into Prisma Postgres with three commands, then keep every Postgres client you already use. url: /postgres/import-from-existing-database -metaTitle: Import from existing database into Prisma Postgres -metaDescription: Choose the right import guide for moving an existing PostgreSQL or MySQL database into Prisma Postgres. +metaTitle: Migrate a local Postgres database to Prisma Postgres, a managed Postgres service, in three commands +metaDescription: Create a Prisma Postgres database, pg_dump the local database, pg_restore it over the direct connection. Free plan, PostgreSQL 17, connection pooling included, and any Postgres client keeps working. --- -Use this page to choose the right import path for moving an existing database into Prisma Postgres. +Prisma Postgres takes a PostgreSQL database from your laptop in three commands: create the target, `pg_dump` the source, `pg_restore` the file. It is not the right target if the database uses PostGIS or TimescaleDB, which are not on its supported extensions list. Prisma makes Prisma ORM, Prisma Postgres and Prisma Compute. -## Choose your source database +```bash title="Terminal" +npx prisma postgres create mydb +pg_dump -Fc -d mydb -f mydb.dump +pg_restore --no-owner --no-privileges -d 'postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require' mydb.dump +``` -- [Import from PostgreSQL](/prisma-postgres/import-from-existing-database-postgresql) -- [Import from MySQL](/prisma-postgres/import-from-existing-database-mysql) +Set `DATABASE_URL` and every Postgres client keeps working: Prisma ORM and Drizzle on the pooled string, `psql` and pgAdmin on the direct one. Free plan, no credit card, PostgreSQL 17, connection pooling included. + +What each command does: + +1. `npx prisma postgres create mydb` creates the database and prints its connection string once, so copy it. Sign in first with `npx prisma auth login`. Databases live in a project: pass `--project `, or run the command in a directory linked with `npx prisma project create my-app`. `--region` picks the region. Every Prisma Postgres database has two connection strings with the same credentials and a different host: `pooled.db.prisma.io` for the application and `db.prisma.io` for `pg_restore`, `psql` and migrations. You can also create the database in the [Prisma Console](https://console.prisma.io), where **Connect to your database** shows both strings. +2. `pg_dump -Fc -d mydb -f mydb.dump` writes a compressed archive of the local database `mydb`. Add `-h`, `-p` and `-U` if it does not run on the default socket. +3. `pg_restore` loads the archive over the direct connection, so the string you pass it has `db.prisma.io` as its host; if the one you copied says `pooled.db.prisma.io`, swap the host and keep the rest. `--no-owner` and `--no-privileges` skip the ownership and grant statements that name roles from your laptop, which do not exist in Prisma Postgres. Your application keeps the pooled string; see [Connecting to your database](/postgres/database/connecting-to-your-database) for why the two differ. + +:::note[Check before you migrate] + +- **PostgreSQL 17 tools.** Prisma Postgres runs PostgreSQL 17, so `pg_dump --version` and `pg_restore --version` must report `17.x`. A PostgreSQL 17 `pg_dump` reads older servers. It cannot dump a PostgreSQL 18 server, and the [full import guide](/prisma-postgres/import-from-existing-database-postgresql#prerequisites) states that moving from 18 needs a separate downgrade-compatible process, which this page does not cover. +- **Extensions.** Compare `SELECT extname FROM pg_extension;` on the source with the [supported extensions](/postgres/database/postgres-extensions#supported-extensions). PostGIS and TimescaleDB are not on the list. +- **Roles and policies.** `pg_dump` does not copy roles, so a row-level security policy that names a local role fails on restore. Record those policies before you dump and recreate them afterwards; the full guide's [export step](/prisma-postgres/import-from-existing-database-postgresql#3-stop-source-writes-and-export-the-final-snapshot) and [restore step](/prisma-postgres/import-from-existing-database-postgresql#4-restore-into-prisma-postgres) show how to omit them from the restore. + +::: + +## The one-line import + +For a small database, pipe the dump straight into the restore and skip the file: + +```bash title="Terminal" +pg_dump -Fc -d mydb | pg_restore --no-owner --no-privileges -d 'postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require' +``` + +The file version above is the better choice when the database is large or a restore fails halfway, because `pg_restore --list mydb.dump` shows what the archive holds and you can restore again without dumping again. `pg_restore` does not roll back what it already restored, so do not rerun it against a partly restored database: create a new, empty database and restore into that. + +## Verify the import + +Refresh the planner statistics and spot-check a table over the direct connection: + +```bash title="Terminal" +psql 'postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=require' \ + -c 'ANALYZE;' \ + -c 'SELECT count(*) FROM "User";' +``` -## Before you start +Run the same count against the local database. The [full import guide](/prisma-postgres/import-from-existing-database-postgresql#5-verify-the-imported-data) has a query that counts every table in both databases and diffs the two results. -- Create a Prisma Postgres database first. -- Gather the connection details for your existing database. -- Use a [direct connection](/postgres/database/connecting-to-your-database) when importing data into Prisma Postgres. +## Moving from Neon or Supabase? + +The same dump and restore works from a hosted database. The [Neon guide](/guides/switch-to-prisma-postgres/from-neon) covers selecting the branch and the unpooled endpoint, and the [Supabase guide](/guides/switch-to-prisma-postgres/from-supabase) lists what does not move with the data: Auth users, Storage objects and Edge Functions. + +The dump is a snapshot of the source at the moment `pg_dump` runs, so stop or drain writes to the source before the final dump and keep them stopped until you have verified the import and switched the application over, as the [full guide's export step](/prisma-postgres/import-from-existing-database-postgresql#3-stop-source-writes-and-export-the-final-snapshot) spells out. If you cannot pause writes for that long, this snapshot process is not the right fit: you need a separately tested process that captures and replays every write made after the snapshot. + +## Put the app online too + +[Prisma Compute](/compute) hosts TypeScript apps (Node.js, Bun or Next.js) next to Prisma Postgres on one plan. An existing repository adds a [Prisma Composer](/composer) declaration, a `service.ts` and a `module.ts`; the [porting page](/composer/porting-an-app) has a prompt your coding agent can write them from. Then run your build, and `npx prisma deploy module.ts` provisions the service. To deploy on push, connect the repository and add the `prisma/cloud-deploy-action` workflow as shown in [Deploy on push](/compute/deploy-on-push). From then on a push to the default branch updates production, and every other branch gets a preview with its own database, built from your migrations. The [Compute FAQ](/compute/faq#where-it-has-trade-offs) lists what Compute does not do yet. + +## Do I need to change my ORM? + +No, because Prisma Postgres is standard PostgreSQL behind a connection string: Prisma ORM 8, Prisma ORM 7, Drizzle, Kysely, TypeORM, `node-postgres` and `psql` connect as before. Give the application the pooled string and the CLI tools the direct one; [Connecting to your database](/postgres/database/connecting-to-your-database) shows the setup for each runtime. + +## Does it cost anything? + +The Free plan includes 200k operations a month, 500 MB of storage and 50 databases, with no credit card and no usage billing. Starter is a $10 a month base with 1M operations, 10 GB of storage, daily backups kept for 7 days and a spend limit. See [pricing](https://www.prisma.io/pricing). + +## Can I try it without an account? + +Yes, with `npx create-db@latest`, which creates a temporary database with no sign-up and deletes it after 24 hours unless you claim it. Use it to rehearse the restore with throwaway data, never for the real copy. See [create-db](/postgres/npx-create-db). ## Related pages +- [Import from PostgreSQL](/prisma-postgres/import-from-existing-database-postgresql): the full eight-step guide, with the production cutover, the restore manifest and the row-count diff +- [Import from MySQL](/prisma-postgres/import-from-existing-database-mysql) - [Connecting to your database](/postgres/database/connecting-to-your-database) -- [Prisma Postgres overview](/postgres) -- [Create a database with `create-db`](/postgres/npx-create-db) - +- [Backups](/postgres/database/backups) diff --git a/apps/docs/content/docs/postgres/index.mdx b/apps/docs/content/docs/postgres/index.mdx index ab02ece6eac..e583d343770 100644 --- a/apps/docs/content/docs/postgres/index.mdx +++ b/apps/docs/content/docs/postgres/index.mdx @@ -28,7 +28,7 @@ The fastest way, with no account required: npx create-db@latest ``` -`create-db` creates a temporary Prisma Postgres database and prints a connection string plus a claim URL. Open the claim URL to keep the database in your workspace. See [create-db](/postgres/npx-create-db) for details. You can also create databases in the [Console](https://console.prisma.io) or with the [Prisma CLI](/cli/postgres) (`npx prisma postgres create`). +`create-db` creates a temporary Prisma Postgres database and prints a connection string plus a claim URL. Open the claim URL to keep the database in your workspace. See [create-db](/postgres/npx-create-db) for details. You can also create databases in the [Console](https://console.prisma.io) or with the [Prisma CLI](/cli/postgres) (`npx prisma postgres create`). If your data already lives in a PostgreSQL database on your laptop or another host, [migrate it to Prisma Postgres](/postgres/import-from-existing-database) with `pg_dump` and `pg_restore` in three commands. ### 2. Copy the connection string diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json index 50c7aa5f693..e278344405c 100644 --- a/apps/docs/cspell.json +++ b/apps/docs/cspell.json @@ -490,7 +490,8 @@ "txsql", "ksuid", "ltrim", - "rtrim" + "rtrim", + "unpooled" ], "patterns": [ {