<!-- cmdz — Deploy a Next.js + Postgres app without writing a Dockerfile. Source: https://www.cmdz.com/blog/nextjs-postgres-without-a-dockerfile -->
# Deploy a Next.js + Postgres app without writing a Dockerfile
> The most common stack on this platform, end to end: detection, the database, migrations on release, environment variables, preview environments and what the whole thing costs.

The most common stack on this platform, end to end: detection, the database, migrations on release, environment variables, preview environments and what the whole thing costs.

17 Jun 2026 · 4 min read · cmdz

Next.js with Postgres is the single most common shape of application here. This is the whole path, including the parts that usually go wrong.

## Deploy first, configure later

```bash
npx @cmdz/cli deploy
```

Detection reads `package.json`, your lockfile and `next.config.mjs` and works out the framework, the package manager and the Node version. If there is a `schema.prisma` or a `drizzle.config.ts` pointing at `postgresql`, it also works out that you need a database — and starts provisioning it in parallel with the build rather than after it.

You do not write a Dockerfile. You may, and if one exists we use it, but the default path does not need it.

## The database

If detection did not find it, or you want to be explicit:

```bash
cmdz services create postgres --size small --attach web
```

It quotes before it creates:

```
instance   1 × small       2 credits/hour
storage    5 GB          150 credits/GB/month
backups    5 GB           10 credits/GB/month
──────────────────────────────────────────
estimated  2 260 credits/month  = € 2.26

after this your limit has € 5.34 of headroom left.
create it? [y/N]
```

Showing the price before the thing exists is a house rule, not a courtesy. It applies to the CLI, the portal and the MCP tools equally.

Attaching injects `DATABASE_URL` into the app. You never copy a connection string, and rotating the credential later does not require a redeploy.

## Migrations belong in the release command

The mistake that costs people an afternoon is running migrations in the build. A build produces an image and may run many times, including for a preview environment you have forgotten about. A **release** command runs once per deploy, after the image is built and before traffic moves.

```toml
[apps.web]
release = "pnpm prisma migrate deploy"
start   = "pnpm start"
```

If the release command fails, the deploy fails and the previous version keeps serving. That is the behaviour you want: a failed migration should never be followed by a new app version talking to an old schema.

## Environment variables

```bash
cmdz env set STRIPE_SECRET_KEY=sk_live_… --app web
cmdz env set NEXT_PUBLIC_SITE_URL=https://linktree.dev --app web
```

Two things to know.

Variables prefixed `NEXT_PUBLIC_` are baked into the client bundle at build time by Next.js, so changing one requires a rebuild, not just a restart. We trigger that for you, but it is worth knowing why the deploy took forty seconds instead of five.

And `cmdz env pull` writes **names, never values**. That is true for you, for your CI, and for an agent. An agent that has to understand your app needs to know a `STRIPE_SECRET_KEY` exists; it has no business knowing what it is.

## Preview environments

Connect the repository and every pull request gets its own URL and its own environment.

By default a preview shares nothing with production: it gets its own database, seeded from your seed script rather than cloned from live data. You can point it at a shared preview database if you would rather, and for most apps you should not.

Previews sleep when nobody visits and wake on the first request, so twenty open pull requests cost close to nothing. They are cleaned up when the branch is.

## Caching and ISR

Next.js caching mostly works the way it does on your machine, with one difference worth knowing: with more than one replica, the in-memory cache is per replica. For anything that must be shared — ISR revalidation state, rate limit counters, sessions — attach Valkey and point the cache handler at it.

```bash
cmdz services create valkey --size small --attach web
```

This is not a cmdz-specific problem; it is what happens anywhere you scale past one instance. It surprises people because it does not happen locally.

## Images

Next.js image optimisation runs in your app, which means it uses your CPU and your memory. On a small instance a page full of unoptimised images is the most likely cause of a memory spike.

Either pre-optimise at build time, or give the app a bit more memory and watch what it does to your run rate in the usage screen. Both are fine; guessing is not.

## The domain

```bash
cmdz domains buy linktree.dev --attach web
```

Registration, zone, A and AAAA records and a certificate, in one command. The renewal price is shown before you confirm — not just the first-year price, because a cheap first year with an expensive renewal is a dark pattern and we are not doing it.

Domains are charged on a card, outside your spending limit. That is deliberate: a paused workload should never be able to cost you a domain name.

## What it costs

Two replicas at 0.5 vCPU and 1 GB, a small Postgres with 5 GB, daily backups, and a normal amount of deploying:

```
compute        € 2.19
memory         € 2.19
database       € 1.46
storage        € 1.05
backups        € 0.07
build minutes  € 0.08
outbound       € 1.26
────────────────────────
               € 8.30 / month
```

Set your limit at € 15 and you have room for a bad week without having room for a bad month. Run it through [the calculator](/pricing#calculator) with your own numbers.

## Set your limit and start.

One click with a passkey, then you verify a payment method once to start your 14-day free trial (€ 10 of credit). After that it is prepaid pay-as-you-go — you only ever spend credit you have already bought, and no invoice ever arrives above the amount you set.

- [Create account](https://app.cmdz.com/signup)
- [Read the docs](https://docs.cmdz.com)
