On this page 9 sections
A Next.js app in a container needs one setting most tutorials skip: output: "standalone", which trims the build down to only the files next start actually needs instead of the full node_modules tree. Get that right and the rest of a Next.js deploy on Ownkube is a normal git push.
This walks through deploying a Next.js app (App Router or Pages Router, both work) on Ownkube Compute: the standalone build, environment variables split between build time and runtime, an optional managed Postgres for apps using an ORM, and automatic TLS.
TL;DR
- Set
output: "standalone"innext.config.jsso the Docker image ships a minimal server instead of the wholenode_modulesfolder. - Push the app with a
Dockerfile, and Ownkube builds it, deploys it, and issues TLS automatically on your*.ownkube.apphostname. NEXT_PUBLIC_*variables get baked into the build; everything else is a runtime environment variable set in the dashboard.- Add an Ownkube-managed Postgres database if you use Prisma or another ORM, and point the connection string at it.
- Turn on horizontal autoscaling once traffic is uneven, since Next.js’s SSR path is CPU-bound per request in a way static pages are not.
1. Enable standalone output
In next.config.js, add the standalone output mode:
/** @type {import('next').NextConfig} */const nextConfig = { output: "standalone",};
module.exports = nextConfig;This changes what next build produces. It emits a self-contained .next/standalone folder with only the files the server needs, plus a minimal server.js entry point, so the runtime no longer needs the full project and node_modules. That keeps the final Docker image small.
2. Write a Dockerfile
A multi-stage build keeps dependencies out of the final image:
FROM node:20-slim AS depsWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci
FROM node:20-slim AS buildWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .# NEXT_PUBLIC_* values must be present at build time; they end up in the client bundleARG NEXT_PUBLIC_API_URLENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URLRUN npm run build
FROM node:20-slimWORKDIR /appENV NODE_ENV=productionCOPY --from=build /app/.next/standalone ./COPY --from=build /app/.next/static ./.next/staticCOPY --from=build /app/public ./public
EXPOSE 3000CMD ["node", "server.js"]The build stage takes NEXT_PUBLIC_API_URL as a build argument because Next.js inlines NEXT_PUBLIC_* variables into the client JavaScript at build time. A value set only at runtime in the dashboard will not reach client-side code; it has to be present when npm run build runs.
3. Push the repo and create the app
Connect the repository from the Ownkube dashboard (app.ownkube.io) to deploy on every push, or deploy the current directory with the okctl CLI:
okctl loginokctl up --name nextjs-app --port 3000Ownkube builds the image, allocates a hostname on *.ownkube.app, and issues TLS automatically, so there is no reverse proxy or certificate to set up by hand.
4. Set your environment variables
Split variables by when Next.js actually needs them:
# Server-only, read at runtime by API routes or Server Componentsokctl vars set nextjs-app AUTH_SECRET="$(openssl rand -base64 32)" --secret
# Client-visible, baked into the bundle at build time, so set it as a build argument (see step 2)okctl deploy build-args nextjs-app --arg NEXT_PUBLIC_API_URL="https://nextjs-app.ownkube.app/api"Environment variables set through Ownkube are encrypted at rest and only injected into the running container, which is why a NEXT_PUBLIC_ value goes in as a build argument instead. Anything without the NEXT_PUBLIC_ prefix stays server-side and never ships to the browser.
5. Add a managed Postgres database, if you use an ORM
A Next.js app using Prisma, Drizzle, or another ORM for a database-backed API route needs a real Postgres instance. Create a database box from the dashboard: New > Database > Postgres, walked through in full in add a managed Postgres database to your app.
It provisions a private (in-region) Postgres box starting at $4 a month plus storage at $0.25 per GB, with backups and point-in-time recovery available on Personal and Team. The database runs as a single instance by default; you can turn on opt-in public access and, on Team, high availability (a primary plus up to two standby instances) per database when you need them.
Link the database to the app, and Ownkube writes its connection string into the app as a secret DATABASE_URL variable and redeploys it:
okctl deploy link nextjs-app <database-id>With Prisma, generate the client in the build stage:
# In the build stage, after copying the schemaRUN npx prisma generateRun prisma migrate deploy as a one-off job instead of in the build. Variables reach the running container rather than the build, and the database is private to its region, so the build cannot reach it.
The slim standalone image leaves out the Prisma CLI and your prisma folder, so give the job its own Dockerfile that installs dependencies and copies the schema, set its command to npx prisma migrate deploy, and link the same database to it. Then trigger it when a release includes new migrations:
okctl deploy link nextjs-migrate <database-id>okctl deploy job-runs trigger nextjs-migrateA purely static or content-fetching Next.js app can skip a database entirely.
6. Scale and watch it run
A fresh Next.js app usually starts fine on a small metered ceiling, for example a 500m CPU limit and 512 MiB of memory, set on the sliders under Settings → Resources.
Turn on horizontal autoscaling if traffic is uneven, so Ownkube adds server instances under load and scales back down when it is quiet. On Ownkube Compute you turn it on through your coding agent (the update_deployment MCP tool) or the API, with a minimum and maximum replica count and a CPU target such as 70%.
Server-side rendering and API routes are CPU work per request in a way a purely static export is not, so an SSR-heavy app benefits more from autoscaling headroom than a mostly-static one.
Metered billing charges CPU and memory on actual per-minute usage, so a quiet Next.js app between visits barely moves the wallet. Logs and metrics for the app live in the dashboard, so a slow render or a route that keeps erroring shows up in one place.
Moving to your own AWS later
If the app needs to run inside your own AWS account later, for compliance reasons or because a client wants their name on the infrastructure, the same Dockerfile and Next.js app move over with no rewrite. Ownkube provisions the instance, wires up the same deploy flow, and brings the logs, metrics, and health checks with it.
Railway only runs your app on its own infrastructure. Our Railway alternatives guide compares Railway with the other options.
FAQ
Do I need output: "standalone", or does the default build work?
The default build works, but it expects the full project and node_modules present at runtime, which makes for a much larger Docker image and a slower deploy. Standalone output is the setting almost every Next.js Docker guide converges on, and it is the one used in the Dockerfile above.
Does Ownkube support the App Router and Server Actions?
Yes, both the App Router and the older Pages Router run the same way, since next start in the standalone build serves whatever the app builds. Server Actions run as part of the same Node process and read server-side environment variables normally.
How do I handle a value that is different between preview and production?
Set the environment variable per app in the dashboard or with okctl vars set <app> NAME=VALUE, and use a separate Ownkube app for each environment you want isolated, each with its own hostname, environment variables, and (if needed) database.
What happens if my wallet balance runs out mid-month?
The app pauses and any attached database keeps its data. Adding credit or the next monthly plan load resumes it. Unused wallet credit rolls over and never expires.
Can I use a custom domain?
An automatic *.ownkube.app hostname with TLS is provisioned for every app the moment it deploys, so you always have a working URL with no configuration. Custom domain support on Ownkube Compute is on the roadmap; it is available today on Starter clusters in your own AWS account.
For the always-current version of this walkthrough, see the Deploy Next.js guide in the docs.
Where Ownkube fits
Next.js wants a slim server image, a clean split between build-time and runtime configuration, and a database only when the app actually uses one, and Ownkube gives you all three without provisioning a cloud account first. Set standalone output, push the Dockerfile, set your environment variables, and the hostname and TLS are already waiting. See Ownkube pricing for the per-minute wallet model, from $5 a month. Deploy my first app.