All articles
Deployment·12 min read

Run Prisma migrations during deployment.

Deploy a Prisma app with PostgreSQL, inspect a deliberately failed release, and recover with a compatible schema change that preserves the data.

Helicarrier team ·

Run Prisma migrations during deployment.

A migration can fail after the application builds successfully. The useful question is what happens next: which version is serving requests, what changed in the database, and whether retrying is safe.

This walkthrough uses three fictional records and three Git tags. First, deploy a working app. Then attempt a deliberately invalid migration. Finally, inspect the failure and add a nullable column with a backfill. You will verify the running app and the database at every stage.

Use disposable data. Create a new project and database for this exercise. The second tag intentionally fails. Do not connect it to an existing application database. An application rollback does not undo database changes.

Before you start

You need a Helicarrier workspace that can create services and a GitHub account with a copy of the Prisma migration example. Make sure Helicarrier’s GitHub connection can access your copy, including the three tutorial tags. If your copy does not include them, fetch and push the tags before continuing.

The example uses Node.js 22, Prisma 7.10.1, and PostgreSQL 16. Its page only reads the three sample records; it has no public write endpoint. The Dockerfile generates Prisma Client while building the image, without requiring a database credential. Prisma’s CLI remains in the runtime image for the release command.

We verified the baseline, failed migration, and recovery on a managed PostgreSQL 16.14 database in Helicarrier. Setup and the first releases used the platform’s MCP tools; the dashboard screenshots show the actual services, and we ran the inspection command and selected the recovery tag through the dashboard. The example’s CI separately checks migration behavior and the read-only Docker runtime.

TagChangeExpected result
01-baselineCreate the table and three rowsThe board reads the base schema
02-failed-migrationAdd a required column without values for existing rowsThe release fails
03-add-summaryAdd a nullable column and backfill the sample rowsThe board displays the summaries

The repository’s default branch contains the corrected app. Disable auto-deploy and follow the tags in order to reproduce the failure. The screenshots use the optional new dashboard experience.

1. Create PostgreSQL and connect the app

Open a dedicated project. Choose New service → Databases → PostgreSQL. Name the service tutorial-prisma-postgres, choose version 16, and use guide as the username and helicarrier_prisma_guide as the database name. Leave the password blank for automatic generation; no optional extensions are needed.

PostgreSQL 16 setup for the separate Prisma tutorial database with an automatically generated password
Create a separate database for the exercise. Select the image to view it at full size.

For this small demonstration, choose Heli DB-S1 with 2 GB of storage and Heli S1 for the web app. Standard full-month reference rates are $3.50 for the database and $3 for the app, before optional add-ons. Review your current dashboard prices before creation; these rates are not a production sizing recommendation. See PostgreSQL plans and platform pricing.

Select Provision, then Apply on the canvas. Keep external database access off. In the same project and environment, create a Web service from your GitHub repository, named tutorial-prisma-board, with internal port 3000. Leave the root directory blank for the standalone repository. The included Dockerfile supplies the build and start commands.

Open the app’s Variables & secrets and add a reference from tutorial-prisma-postgres’s Connection URL to DATABASE_URL. This keeps the credential in the service connection rather than your code.

DATABASE_URL linked to tutorial-prisma-postgres using an internal URL reference
The app uses a private service reference; no connection string is copied into the guide. Select the image to view it at full size.

An initial deployment may start before you finish the settings. It is expected to be unready until the database reference and release command are configured. Complete the next step, then deploy the baseline tag explicitly.

2. Release the working baseline

In Service settings, turn Auto-deploy on push off. Under Build settings, select Force Dockerfile, use Dockerfile as its path, and set the release command to:

Normal release command
npm run db:migrate

Keep the read-only filesystem enabled. Save the settings, then open Deployments → Deploy a specific ref, enter 01-baseline, and deploy. Using a specific ref does not change the branch the service tracks.

Dockerfile settings with npm run db:migrate as the release command and read-only filesystem enabled
The release command runs from the new image before its traffic rollout. Select the image to view it at full size.

The command wraps prisma migrate deploy. It applies committed migration files and prints sanitized status messages. It does not generate new migrations or detect all schema drift; develop and review new migration files before deployment. See Prisma’s migrate deploy reference.

Open the app’s generated URL. Confirm 01 · Base schema, 3 / 3 intact, and PostgreSQL connected. Its /health endpoint must return HTTP 200. This query checks the running app’s required schema; it does not prove that every attempted migration succeeded.

The deployed migration board showing the base schema and three original PostgreSQL records
Start from a verified working application. Select the image to view it at full size.

3. Deploy the intentionally broken migration

Use Deploy a specific ref again, this time with 02-failed-migration. Its migration tries to add a required summary column to rows that have no summary:

Deliberately invalid SQL · disposable database only
BEGIN;
ALTER TABLE "guide_release_notes"
  ADD COLUMN "summary" TEXT NOT NULL;
COMMIT;

PostgreSQL cannot satisfy that constraint for the existing rows. The command exits unsuccessfully and Helicarrier stops the release before switching traffic to the new app. Prisma may surface P3018 or an aborted-transaction message; a subsequent retry while the failed attempt remains unresolved is blocked with P3009.

Reload the public board. It should still show the baseline and three records. The service’s failed badge describes the latest deployment; verify the active deployment and readiness endpoint to understand what is serving. This particular migration is transactional, and we check below that its schema change rolled back. A release failure alone is not a guarantee that a database was left unchanged.

Deployment history showing the intentionally failed migration release
A failed release must be investigated before retrying. Select the image to view it at full size.

4. Inspect what actually changed

Open the tutorial PostgreSQL service and its query console. Run these read-only checks:

Inspect rows, columns, and migration history
SELECT id, title FROM guide_release_notes ORDER BY id;

SELECT column_name, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'guide_release_notes'
ORDER BY ordinal_position;

SELECT migration_name,
       finished_at IS NOT NULL AS finished,
       rolled_back_at IS NOT NULL AS rolled_back
FROM _prisma_migrations ORDER BY started_at;

Expect the original three titles, only id and title columns, and two migration attempts: a completed baseline plus an unfinished, not-rolled-back 20261021000200_add_summary. If any of those checks differs, stop. Do not use this exercise’s recovery command to repair an unknown database state.

On an authorized service shell, npm run db:inspect prints the same tutorial checks as booleans and known migration names, without connection details. Keep raw connection strings and private deployment infrastructure out of support messages and screenshots.

The live app shell confirming intact sample rows, an absent summary column, and an unresolved failed migration
Read-only inspection confirms the specific preconditions for this recovery. Select the image to view it at full size.

5. Apply the reviewed correction

The corrected migration adds summary TEXT, which permits null, and fills in values for the three known sample rows. The new application reads the optional field. Older code can still read its original columns and insert a row without supplying a summary.

For one deployment only, change the release command to:

One-time recovery command · only after inspection
npm run db:recover-demo && npm run db:migrate

Save without deploying the default branch, then deploy 03-add-summary explicitly. The recovery script checks the known rows, columns, and migration history again. Only if they match the expected failure does it run prisma migrate resolve --rolled-back 20261021000200_add_summary. The second command then applies the correction.

Resolving is bookkeeping, not rollback. Marking a migration rolled back does not undo SQL. Here it is valid because inspection confirms that the failed transaction left no schema change. We correct a migration that never applied successfully; do not edit the history of a successfully applied migration. See Prisma’s recovery workflow.
The one-time guarded recovery and migration command configured before the corrected deployment
Restore the normal command after this single recovery release. Select the image to view it at full size.

After success, restore npm run db:migrate as the release command. The recovery helper deliberately refuses a database that has already recovered, so leaving it in place would block the next deployment.

6. Verify the expansion and repeat the release

The board should now show 03 · Compatible expansion, Summary backfilled, and three records. The first summary reads “Deploy from a Git repository.” Check /health again, then repeat deployment of 03-add-summary with the normal release command. The migration should be skipped as already applied and the records should remain unchanged.

The deployed migration board showing all three summaries after the compatible schema expansion
Verify the data after the corrected release and again after a repeat deployment. Select the image to view it at full size.

Confirm that summary remains nullable and migration history now contains the completed baseline, the rolled-back failed attempt, and the completed corrected attempt. For a larger application, plan expansion, batched backfill, and any later constraint changes separately. Remove an old column or require a new value only after every old reader and writer has moved on. See Prisma’s migration customization guidance.

If a step fails

  • Tag not found: check that your GitHub copy includes all three tags and that the connected GitHub account can access it.
  • Release cannot start: confirm the Dockerfile was used, the Prisma CLI is present in the image, PostgreSQL is active, and DATABASE_URL is a private reference on the app.
  • P3009 after changing the SQL: editing a file does not resolve the failed database attempt. Inspect the actual state and follow the recovery procedure; do not delete migration history rows.
  • Recovery refused: the helper detected a different state or an unavailable database. Inspect it rather than bypassing the guard. If recovery already succeeded, restore the normal migration command.
  • Page opens but readiness fails: the static page can load while its database query fails. Use /health, the displayed records, and deployment logs together.

Clean up, then apply the pattern carefully

When finished, remove only tutorial-prisma-board and tutorial-prisma-postgres through their service settings. Verify the names first and export anything you intend to keep. Check the project afterward for leftover resources.

For real applications, establish and test a backup and restore path before risky schema changes. Continue with build and release settings, application rollback behavior, or the Next.js and PostgreSQL deployment guide.

Keep exploring

Another useful next step.

Ready when you are

Make something.
We’ll keep it running.

Bring your code. Give your next idea a home on Helicarrier Cloud.