Skip to main content
A Node.js application on Sevalla reaches a Sevalla PostgreSQL database over an internal connection, and Prisma ORM reads the database’s address from the DATABASE_URL environment variable. The build generates the Prisma client from your schema, and a job applies your migrations with prisma migrate deploy before each deployment, so new code never runs against an old schema. This guide adds a Product table to an Express application with Prisma ORM 7.

At a glance

What this guide adds to the Express application:

Environment variables

The Express application’s own variables are in its table. The internal connection also adds DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD, which Prisma doesn’t read.

Before you begin

  • An Express application deployed on Sevalla, as in Deploy Express. It pins Node.js 24; Prisma ORM 7 needs Node.js 20.19, 22.12, or 24.0 and later.
  • PostgreSQL on your computer, to create migrations against. With Docker:

Create the database

1

Create the database

In the sidebar, click Databases, then click Create database. Under Database, choose PostgreSQL and the newest version. Enter orders-db as the Display name, choose the same Data center as the application and a Size, then click Create database. See Create a database for every field.
2

Connect it to the application

In the sidebar, click Applications, then click the application. Click the Networking tab. Under Connected services, click Add internal connection. Choose orders-db as the Service and turn on Add environment variables to the application. In the list of variables, change the key DB_URL to DATABASE_URL, then click Add internal connection.
DATABASE_URL holds the database’s internal address, postgres://<user>:<password>@<host>:<port>/<database>, which Prisma reads as is. The connection also adds DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD, which this guide doesn’t use. The variables apply at the next deployment. See Connect an application.

Install Prisma

In the project, install Prisma ORM 7 and create its files:
Install prisma and dotenv as dependencies, not dev dependencies: the migration job runs the CLI from the application’s image. With both as dependencies, Ingot and the Dockerfile can remove every dev dependency after the build. @7 keeps every Prisma package on the same major version. prisma init creates:
  • prisma/schema.prisma, the schema, with a generator that writes the client to generated/prisma.
  • prisma7.config.ts, the CLI’s configuration, which loads .env and reads the database address from DATABASE_URL.
  • .env, with a placeholder DATABASE_URL.
  • A /generated/prisma line in .gitignore, so the generated client stays out of the repository.
Keep .env out of the repository too, then point DATABASE_URL in it at the database on your computer. Sevalla doesn’t read this file: the application gets DATABASE_URL from the internal connection.
.env

Define the schema

Add a model to the end of prisma/schema.prisma:
prisma/schema.prisma
Create the first migration and apply it to the database on your computer:
Prisma writes the SQL to prisma/migrations/<timestamp>_init/migration.sql. Commit the prisma/migrations folder: the migration job applies what’s in it.

Generate the client during the build

The generated client isn’t in the repository, so the build creates it. Make prisma generate the build script, then run it once on your computer:
Ingot runs the build script during every build, after it installs the dependencies, so the client always matches the committed schema. prisma generate doesn’t connect to the database.

Query the database

Create one client for the whole application:
db.js
The generated client is TypeScript. Node.js 24 runs .ts files outside node_modules by stripping their types, so the project needs no compile step. Add routes to server.js that list and create products:
server.js
To try it on your computer, start the server with the variables from .env, then create a product:
The response is the new row, such as {"id":1,"name":"Coffee mug","createdAt":"2026-10-29T18:05:24.848Z"}.

Run migrations before each deployment

A job with the start policy Before deployment runs once per deployment, after the build and before the new version gets traffic:
1

Create the job

On the application’s Processes tab, click Create process, then choose Job.
2

Set its command

Enter Migrate as the Name and npx prisma migrate deploy as the Custom start command. Keep the Start policy at Before deployment and the Resource at H1, then click Create process.
The Create process sheet with Job selected, the Name and Custom start command fields, the Start policy set to Before deployment, and the H1 resourceThe Create process sheet with Job selected, the Name and Custom start command fields, the Start policy set to Before deployment, and the H1 resource
prisma migrate deploy applies the migrations in prisma/migrations that the database doesn’t have yet, and never resets data or creates migrations, unlike prisma migrate dev. If the job fails, the deployment fails and the previous version keeps serving traffic. Ingot recommends this job for any project with a prisma/migrations folder; Get Dockerfile lists it in processes.

Deploy and verify

Commit and push the changes, package-lock.json and prisma/migrations included. With auto-deploy on, the push starts a deployment; otherwise, start one with Deploy now on the application’s Overview tab. When the deployment’s status changes to Deployed:
  1. Open the deployment on the Deployments tab. Its build log shows the client being generated, for example ✔ Generated Prisma Client (7.10.1) to ./generated/prisma in 41ms, and, during the rollout, Migrate has been successful. On the Logs tab, the Migrate process’s output ends with All migrations have been successfully applied.
  2. Create a product and list the products. Use your application’s domain instead of storefront-x7k2m.sevalla.app:
    The second command returns [{"id":1,"name":"Coffee mug","createdAt":"..."}].
  3. On the database’s SQL studio tab, the Product table has the row. See Database studio.
If a request returns 500 or the job fails, read the application’s runtime logs:

Size the connection pool

Each PrismaClient keeps a pool of up to 10 connections, node-postgres’s default, in every instance of every process that creates one. The database accepts a limited number of connections: run SHOW max_connections; in the database’s SQL studio to see its limit. PostgreSQL keeps 3 of them for administration (superuser_reserved_connections), so with a limit of 100, all processes together can open 97. Add up the pools before you scale: a web process with 4 instances and a background worker with 2 open up to 60 connections, plus 1 for the Migrate job and any you open from your computer. When the total gets close to the limit, lower the pool size per instance with max:
db.js
With autoscaling, count the maximum instance count. When every connection your application can open is in use, new connections fail with remaining connection slots are reserved for roles with the SUPERUSER attribute.

Deploy with a Dockerfile

Ingot needs no Dockerfile. Write your own when you want full control of the image: the base image, the system packages and every layer. Add a Dockerfile and a .dockerignore to the root of the repository:
Dockerfile
What Prisma needs in the image: the generated client in generated/prisma; @prisma/client and @prisma/adapter-pg, which run queries without a native engine; and, for the Migrate job, the prisma CLI with the schema engine that npm ci downloads for Debian with OpenSSL 3, plus prisma/, prisma7.config.ts and dotenv. The job runs npx prisma migrate deploy from the same image.
.dockerignore
Commit and push both files. Ingot doesn’t use the Dockerfile, so you can keep it in a repository that builds with Ingot, but it adds the .dockerignore patterns to its own, and its build plan lists them in an Ignored row. Then switch the application to the Dockerfile build strategy:
1

Choose the Dockerfile build strategy

On the application’s Settings tab, in the Build strategy section, click Update build strategy. Click Dockerfile, keep the Dockerfile path Dockerfile and the Docker context ., then click Update build strategy.
2

Deploy

Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
The build log starts with 📦 Building and pushing docker image using Dockerfile..., then shows each step of your Dockerfile and, during the rollout, Migrate has been successful. curl https://storefront-x7k2m.sevalla.app/products returns the same products as before, and the deployment’s Dockerfile tab shows your file. See Dockerfile.

Deploy changes

To change the schema, edit prisma/schema.prisma, run npx prisma migrate dev --name <change> on your computer, and commit the new folder in prisma/migrations with the code that uses it. The next deployment generates the new client during the build, and the Migrate job applies the migration before the new code starts. Make each migration work with the code that’s still running until the rollout ends: add a column before the code reads it, and drop it in a later deployment, after no code uses it. See Deployments.

Deploy with the API

This sequence adds the database and the migration job of this guide to the application from Deploy Express through the Sevalla API, so an agent or a script can run it as is. APP_ID is that application, and the changes on this page are pushed to its branch. Each step names the operation, shows the curl command, and gives the matching CLI command and MCP server tool. You need an API key with the Company developer role in the SEVALLA_API_KEY environment variable (the CLI reads the same key from SEVALLA_API_TOKEN), and curl, jq and openssl. A call that fails returns status and message instead of the resource, and jq -r .id prints null: stop and read the message.
1

Look up the location and the database size

Get application (getApplicationV3) returns the application’s location in cluster_id; the database must be in the same one. List database resource types (listDatabaseResourceTypesV3) returns the database sizes; DB1 is the smallest.
CLI: sevalla apps get "$APP_ID" and sevalla resources db-types. MCP: get_application and get_platform_options.
2

Create the database

Create database (createDatabaseV3) starts creating a PostgreSQL 18 database with a password you generate. List database versions (listDatabaseVersionsV3) lists the versions of a location.
CLI: sevalla databases create --name orders-db --type postgresql --db-version 18 --cluster "$CLUSTER_ID" --resource-type "$DB_SIZE_ID" --db-name storefront --db-user storefront --db-password "$DB_PASSWORD". MCP: create_database, which generates the password and doesn’t return it; with connect_to_application_id and env_var_name set to DATABASE_URL, it also connects the database and stores the connection URL, which replaces the next three steps as postgresql://..., which Prisma reads like postgres://.
3

Connect the database

Create internal connection (createDatabaseInternalConnectionV3) connects the database to the application over the private network. Unlike the dashboard, it adds no environment variables: a later step adds them.
CLI: sevalla databases connections internal-create "$DB_ID" --target-id "$APP_ID" --target-type app. MCP: sevalla_api_create with createDatabaseInternalConnectionV3.
4

Read the database details

Get database (getDatabaseV3) returns the database’s internal hostname and port. The loop waits until its status is ready.
CLI: sevalla databases get "$DB_ID" --json. MCP: get_database.
5

Add DATABASE_URL

Create environment variable (createEnvironmentVariableV3) adds DATABASE_URL in the format the dashboard uses, available during runtime only: prisma generate doesn’t need it during the build.
CLI: sevalla apps env-vars create "$APP_ID" --key DATABASE_URL --value "postgres://..."; the CLI makes it available during the build too. MCP: set_environment_variables with runtime true and buildtime false.
6

Create the migration job

List process resource types (listProcessResourceTypesV3) returns the H1 size, and Create process (createProcessV3) adds the Migrate job. A job needs scaling_strategy and job_start_policy; beforeDeployment is the dashboard’s Before deployment.
CLI: sevalla apps processes create "$APP_ID" --name Migrate --type job --resource-type "$H1_ID" --command "npx prisma migrate deploy" --job-start-policy beforeDeployment. MCP: sevalla_api_create with createProcessV3 and the same body.
7

Deploy

Trigger deployment (triggerDeploymentV3) builds the latest commit of the branch and rolls it out.
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
8

Wait for the deployment

Get deployment (getDeploymentV3) returns the deployment’s status: waiting, then inProgress, then success or failed. The loop polls every 15 seconds for up to 30 minutes and keeps polling through a failed request.
CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.
9

Check the deployment

Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, Get application runtime logs (getApplicationRuntimeLogsV3) what the Migrate job printed, and List application domains (listApplicationDomainsV3) the application’s domain:
The output, with the time of your request in createdAt:
CLI: sevalla apps deployments logs "$DEPLOYMENT_ID" --app-id "$APP_ID" --search "Migrate has been", sevalla apps logs runtime "$APP_ID" --search "migrations have been" and sevalla apps domains list --app-id "$APP_ID". MCP: get_deployment_logs, search_runtime_logs and get_application.