What is detected
When the repository hasnext in package.json dependencies, the detector picks the Next.js preset: port 3000, health path /, a Railpack build, and a volume hint for /app/.next/cache. It also reads next.config.js, .mjs, .ts or .cjs:
The detector shows its reasons in the create-app form. Check the start command it proposes before you create the app.
Choose a build path
There are two ways to build, and you pick one per app.- Railpack (the preset). No files to add. A plain app starts with
npm run start. Withoutput: 'standalone'(and nooutputFileTracingRoot) the build copies.next/staticandpublicinto.next/standalone, startsnode .next/standalone/server.jsand setsHOSTNAME=0.0.0.0;PORTcomes from the app port. - Your own Dockerfile. Use this for a monorepo with a custom
outputFileTracingRoot, or when you want the smallest image and a non-root user.
node:22-alpine with a non-root user. It expects output: 'standalone' in your config and a build script in package.json. It does not install sharp; see below.
Images and sharp
Next.js usessharp to optimize images in a production server. Add it as a dependency of your app (npm install sharp) so the standalone trace includes it. Without it Next.js falls back to a slower path and logs a warning. The generated Dockerfile does not add it for you. The arm64 case has not been verified on a real arm64 host.
Keep the cache across deploys
The preset suggests a volume namednext-cache at /app/.next/cache. It holds the image optimization cache and ISR pages, so they survive a redeploy. In app.yaml:
--framework nextjs flag on apps create, the volume is attached for you if you did not set one.
The volume belongs to one container. See the replica section below.
Health check
The preset uses/ as the health path. A page that renders data can be slow or fail for reasons that do not mean the process is down, so add a cheap route and use it. In the App Router, create app/api/health/route.ts:
Shutdown and keep-alive
On a deploy, Levelrail sendsSIGTERM to the old container and waits up to APP_RECONCILE_STOP_GRACE (default 10s) before SIGKILL. If your pages take long to finish, raise it. See Stop grace.
Node closes idle keep-alive connections after a few seconds by default, while the ingress keeps its own connections open for APP_INGRESS_IDLE_TIMEOUT (default 2m). Recent standalone server.js files read KEEP_ALIVE_TIMEOUT (milliseconds) and apply it. Check the file in your build before you rely on it. This has not been tested against the ingress here.
NEXT_PUBLIC variables are build time values
Next.js writesNEXT_PUBLIC_* values into the JavaScript at next build. Changing one needs a new build. A restart is not enough.
What Levelrail does:
- Plain (non-secret) app env variables whose names start with
NEXT_PUBLIC_are passed to the build. The list of prefixes isAPP_STATIC_BUILD_ENV_PREFIXES. - On the Railpack route, the values are part of the build cache key, so a changed value does not reuse an old layer.
- With your own Dockerfile, a value is passed only if the Dockerfile declares
ARG NAME. AddARG NEXT_PUBLIC_API_URLbeforeRUN npm run build. The Dockerfile printed by the preset does not declare any, so add the ones you use. - Secrets are never passed to a build. Do not use a secret for a
NEXT_PUBLIC_value; it would be public anyway. - The build log has a
build envline that names the keys passed, never the values.
Running more than one replica
Several things are per instance today. Read this before you setreplicas above 1.
Server Actions key. Next.js encrypts Server Action identifiers with a key it generates for each build. Instances that do not share a key can fail actions that were started on another instance. Set the same key on every replica. Generate one and store it as a secret:
revalidatePath and revalidateTag act on the instance that handled the call, so other replicas can keep serving the old page until they revalidate themselves. The next-cache volume does not change this: a named volume is scoped to one service on one node.
The usual fix is a shared cache handler: set the cacheHandler option in next.config to a module that reads and writes the cache in a shared store such as Redis, and set cacheMaxMemorySize: 0 so the in-memory copy does not shadow it. You write or choose that module yourself.
What Levelrail does today: it can run a Redis database next to the app and give you its connection string. What it does not do: provide or configure a cache handler, or share .next/cache between replicas.
If you do not need several replicas, run one. A single instance has none of these problems.
Related
- Framework presets
- App spec reference
- Environment variables and secrets
- Build performance
- Self-host Next.js
Server Actions key
For Next.js 14 or newer apps created by the wizard, Levelrail generates aNEXT_SERVER_ACTIONS_ENCRYPTION_KEY once, stores it as an app secret and gives it to every replica at runtime, so Server Actions work across replicas and redeploys. A redeploy never rotates it; delete the secret to rotate. Turn it off with the runtime setting nextjs.generate_actions_key. Secrets never reach builds, so if you rely on the key at build time, set the same key in the build env yourself.