Guides
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.
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
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:
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.
[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
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.
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
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 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.