Deploy your first app
Take a TypeScript app from your terminal to a live URL on Prisma Compute with Prisma Composer, starting from scratch or from an app you already have.
Prisma Compute is serverless hosting for TypeScript apps. It runs your app right next to your Prisma Postgres database, so the trip from app to data stays short. Apps are declared and deployed with Prisma Composer: you describe your app's services in TypeScript, and one command stands them up on Compute. By the end of this page you have a live URL.
Start from wherever you are:
- You're starting from scratch. Declare a new app and run it locally, then deploy it.
- You already have an app. Add a declaration around your server code, then deploy it.
Both paths meet in the same deploy step, because Composer treats every app the same way. A deployed app becomes a project, deploying with no flags targets production, and --stage <name> deploys an isolated copy of the whole app as a preview branch of the same project. Local runs never talk to the platform, so you need credentials only when you deploy. For the platform's resource model, see Compute.
Prerequisites
Before you start, make sure you have:
- Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer. Composer hands your TypeScript entry file straight to Node, and running
.tsfiles directly needs 22.18.0 or later. On older versions, Composer commands stop atERR_UNKNOWN_FILE_EXTENSION. - Bun. Prisma Compute runs deployed apps on Bun, so your server code targets it, and
bun buildproduces the self-contained entry file a deploy needs. - A Prisma Data Platform account, free to create. You only need it for the deploy step; local development runs without an account.
1. Get an app declared with Composer
A Composer app is your server code plus a small declaration: what each service is called, how it is built, and what it depends on. Pick the path that matches your starting point.
Start from scratch
Set up the project as in Composer getting started, step 1: install @prisma/composer and @prisma/composer-prisma-cloud, install prisma as a dev dependency, and add the tsconfig.json. You do not pin effect: Composer pins it, and the override in When prisma.config.ts fails on an effect version conflict is only for a tree where another dependency hoists a different version. Then a minimal one-service app is four files.
The server is ordinary Bun code. The one Composer-specific line reads the port from service.port(); bind 0.0.0.0, because Compute routes external HTTP to the VM and a loopback-only listener would be unreachable:
import service from "./service.ts";
Bun.serve({
port: service.port(),
hostname: "0.0.0.0",
fetch: () => new Response("Hello from Prisma Compute"),
});The service declaration names the service and points at your built output:
import node from "@prisma/composer/node";
import { compute } from "@prisma/composer-prisma-cloud";
export default compute({
name: "web",
deps: {},
build: node({ module: import.meta.url, entry: "../dist/server.mjs" }),
});The root module is the app: it provisions the service. The deploy config next to it is the composer section of prisma.config.ts, read only by the CLI:
import { module } from "@prisma/composer";
import webService from "./src/service.ts";
export default module("my-app", ({ provision }) => {
provision(webService);
});import { defineConfig as composer } from "@prisma/composer/config";
import { nodeBuild } from "@prisma/composer/node/control";
import { prismaCloud, prismaState } from "@prisma/composer-prisma-cloud/control";
import { definePrismaConfig } from "prisma/config";
export default definePrismaConfig({
composer: composer({
extensions: [prismaCloud({ region: "us-east-1" }), nodeBuild()],
state: prismaState(),
}),
});The region is where the first deploy creates the project. Pick any region listed under Known limitations, or leave it out and set PRISMA_REGION when you deploy; a project that already exists keeps its region.
You own the build; Composer assembles what you built. Add a build script that produces one self-contained file per service:
{
"scripts": {
"build": "bun build src/server.ts --target=bun --outfile dist/server.mjs"
}
}Now run the app on your machine. No account or credentials are involved:
bun run build
bunx prisma dev module.tsdev prints the service's local URL; curl it and you get Hello from Prisma Compute. See Local development for the local database, storage, and log workflow.
From here the app grows by declaration: deps: { db: rawPostgres() } gives the service a Prisma Postgres database (or postgres(contract) from @prisma/composer-prisma-cloud/orm for one typed by Prisma ORM), bucket() gives it object storage, and a second service with a typed contract turns cross-service calls into checked function calls. When the app runs locally, continue to Deploy.
Bring an app you already have
Porting an app does not mean rewriting it. Your server code stays the server, your build stays the build, and you add a declaration around them. The steps below port a two-service app, a notes API on Postgres and an uploads service on S3 credentials, to a Composer app that provisions its own database and bucket. For a Next.js service, use the nextjs build adapter with output: "standalone" instead; Port an existing app covers that variant.
1. Install the Composer packages. Same as step 1 of the scratch path: install @prisma/composer and @prisma/composer-prisma-cloud, and install prisma as a dev dependency. Composer pins effect for you; if one of your existing dependencies pins a different effect, use the override in When prisma.config.ts fails on an effect version conflict.
2. Declare each service. A service.ts next to each server names the service, declares what it consumes as dependencies, and points at your built output. A Postgres connection becomes rawPostgres(); S3 credentials become bucket():
import node from "@prisma/composer/node";
import { compute, rawPostgres } from "@prisma/composer-prisma-cloud";
export default compute({
name: "api",
deps: { db: rawPostgres() },
build: node({ module: import.meta.url, entry: "../../dist/api.mjs" }),
});import node from "@prisma/composer/node";
import { bucket, compute } from "@prisma/composer-prisma-cloud";
export default compute({
name: "files",
deps: { store: bucket() },
build: node({ module: import.meta.url, entry: "../../dist/files.mjs" }),
});3. Provision everything in one module. The root module owns the resources and wires them to the services; the deploy config next to it is the same one the scratch path uses:
import { module } from "@prisma/composer";
import { bucket, rawPostgres } from "@prisma/composer-prisma-cloud";
import apiService from "./src/api/service.ts";
import filesService from "./src/files/service.ts";
export default module("my-app", ({ provision }) => {
const db = provision(rawPostgres({ name: "database" }));
const store = provision(bucket({ name: "uploads" }));
provision(apiService, { deps: { db } });
provision(filesService, { deps: { store } });
});4. Port the environment reads. This is the only change to your server code, and it is what makes every environment work without hand-wired configuration:
- The port: read
service.port()instead ofprocess.env.PORT, and bind0.0.0.0. - Dependency values: read
service.load()instead of connection-string and credential env vars. The Postgres dependency deliversdb.urlfor whatever client you already use; the bucket delivers the standard S3 set. - Everything else (flags, regions, external URLs, third-party credentials) becomes the service's input schema.
import { SQL } from "bun";
import service from "./service.ts";
// before: new SQL({ url: process.env.DATABASE_URL })
const { db } = service.load();
const sql = new SQL({ url: db.url, max: 1, idleTimeout: 10, prepare: false });
Bun.serve({ port: service.port(), hostname: "0.0.0.0", fetch: handler });import service from "./service.ts";
// before: endpoint, bucket, and keys from four S3_* env vars
const { store } = service.load();
const s3 = new Bun.S3Client({
endpoint: store.url,
bucket: store.bucket,
accessKeyId: store.accessKeyId,
secretAccessKey: store.secretAccessKey,
});5. Build and verify locally. Your build must produce one self-contained entry file per service; keep your own build if it already does, or use one bun build --target=bun line per service. Then run the whole declaration locally:
bun run build
bunx prisma dev module.tsdev provisions a local Postgres and a local stand-in bucket with working credentials, and every service appears in the startup output with a local URL, so you can exercise the database and upload paths end-to-end before touching the platform. Then continue to Deploy: the same deploy command provisions the real database and bucket alongside the services.
2. Deploy
Sign in once. This is the only interactive step, because it opens your browser:
bunx prisma auth loginThe session is stored on your machine and every later command reads it, including commands a coding agent runs in your directory. Confirm it any time with auth whoami. In CI or a headless environment, set PRISMA_SERVICE_TOKEN and PRISMA_WORKSPACE_ID instead; see Deploying.
deploy does not build for you, so build first, then deploy:
bun run build
bunx prisma deploy module.tsThe CLI creates a project named after your root module in the region from prismaCloud({ region }) or PRISMA_REGION (without one, the first deploy stops with project "<name>" does not exist yet and no deploy region is configured), provisions the services on Compute and any databases on Prisma Postgres, wires the dependencies, and starts everything. The project name identifies the app in your workspace: redeploying reuses the project this module created, and a same-name project whose hosted state the CLI cannot verify stops the command with HostedStateBootstrapError. Deploy under another name with --name <unique-name> when that happens.
3. Verify the deployment
A deploy finishes by printing what it made: each service, the platform resource it became, and its public URL:
my-app
└─ web compute-service cps_abc123
https://xyz.ewr.prisma.buildRequest the printed URL to confirm the app responds:
curl https://xyz.ewr.prisma.buildYou can also inspect everything in the Console: projects, branches, services, deployments, and domains.
4. Ship changes and environments
Re-deploying is idempotent: build again, deploy again, and the platform applies the difference instead of recreating everything. That includes removals: delete a provision from your module and the next deploy deletes the resource; see Removing resources. Production and previews are separated by stages:
bunx prisma deploy module.ts # production
bunx prisma deploy module.ts --stage staging # a persistent staging environment
bunx prisma deploy module.ts --stage pr-42 # one environment per PRA stage is a complete, isolated copy of the app: its own services, databases, and configuration, sharing only the code with production. In platform terms it is a preview branch of the same project. Tearing a stage down needs the control API with service-token credentials, or a branch deletion in the Console; see Destroying. Deploying covers CI, deploy state, and teardown in full.
Hand it to your agent
You can let a coding agent do the work. For an agent with its own credential, permission policy, and approval flow, see Enroll your agent. For the simplest path, sign in once yourself (step 2). The browser sign-in is the one step an agent can't do for you. After that, anything running in your environment inherits the session, including your agent. The @prisma/composer package ships an agent skill that teaches your agent the installed version's API; npx prisma@latest init sets the project up so the skill stays in sync across upgrades. Paste this into your agent and fill in the blanks:
Build [what you want] as a Prisma Composer app and deploy it to Prisma Compute using `npx prisma@latest`.
Notes:
- After installing @prisma/composer, install its agent skill so you use the real API: `npx prisma skills sync` (the skill ships inside the package; run `npx prisma skills list` to confirm).
- Before deploying, run `npx prisma auth whoami`; if I am not signed in, stop and ask me to run `npx prisma auth login` (it opens a browser).
- Composer needs Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer, and server code targets Bun. Read the port with `service.port()` and bind 0.0.0.0. For a Next.js service, use the nextjs build adapter and set `output: "standalone"`.
- Composer does not build; produce one self-contained entry file per service (for example `bun build --target=bun`), then run it locally with `npx prisma dev module.ts` and verify each service's local URL responds.
- Deploy with `npx prisma deploy module.ts` and verify the printed public URL with curl.
- To give a service a database, declare `deps: { db: rawPostgres() }` (imported from @prisma/composer-prisma-cloud) and build the client from the injected `db.url`; do not read connection strings from process.env. For a database typed by Prisma ORM, use `postgres(contract)` from @prisma/composer-prisma-cloud/orm instead.
- A new project needs a region: set `prismaCloud({ region: "us-east-1" })` (or another region) in the `composer` section of prisma.config.ts, or set PRISMA_REGION, before the first deploy.
- Current docs: https://www.prisma.io/docs/composer.md and https://www.prisma.io/docs/prisma-compute/deploy.mdFor example:
Build a Bun API with a /todos endpoint backed by an in-memory list as a Prisma Composer app and deploy it to Prisma Compute using `npx prisma@latest`.What's next
- Composer getting started: the two-service version, with a typed contract between services.
- Databases: give a service a Prisma Postgres database, plain or typed by a Prisma ORM contract.
- Deploying: stages, CI, deploy state, and teardown.
- Local development: run the app, database, and storage on your machine with the runtime you deploy.
Start a TypeScript app with Postgres on Prisma for free
From an empty directory to a live TypeScript app with a Prisma Postgres database on Prisma Compute, with the Free plan limits listed.
Enroll your coding agent: it works on preview branches and asks before changing production
Give a coding agent its own Prisma credential and a permission policy you control, so it asks you before sensitive API actions and every request it makes is logged.
