knext
Learn knext

3 · Configure the deploy

The health route that everyone forgets, and the config file that describes the deployment.

Two files. One of them is the single most common reason a first deploy sits at Ready=False.

The health route — do not skip this

app/api/health/route.ts
export const dynamic = 'force-dynamic';

export function GET() {
  return Response.json({ status: 'ok' });
}

Why it matters more than it looks. The operator points Knative's readiness and liveness probes at /api/health by default. create-next-app does not generate that route.

kn-next create writes this file for you. If you scaffolded the app with kn-next create, the route above is already there — skip ahead. This section is for an existing app you are adding knext to, which is where the missing route bites.

Without it, the probe gets a 404. The revision never becomes Ready. Your deploy "succeeds", the pod starts, and then kn-next status reports Ready=False forever — with nothing obviously wrong in your application logs, because nothing is wrong with your application. It is being asked a question it cannot answer.

That is enough to go Ready. For a real app you want a health check that reports on its dependencies too — @getknext/lib ships checkShallowHealth() and checkDeepHealth() for that.

Keep the probe route shallow. It should answer "is this process serving?", never "is every dependency awake?". knext scales databases and caches to zero, so an asleep dependency is the normal state — a probe that dials one flaps readiness on every cold wake, and liveness then restarts a pod that was perfectly healthy. Put dependency checks on a separate path (/api/health/deep by convention) and point alerting at that, not Kubernetes.

Prefer a different path? Set healthCheckPath in the config below and the operator points the probes there instead.

Describe the deployment

Create kn-next.config.ts beside next.config.ts:

kn-next.config.ts
import type { KnativeNextConfig } from '@getknext/core';

const config: KnativeNextConfig = {
  name: 'acme',
  registry: 'registry.example.com/acme',
  storage: {
    provider: 'gcs',
    bucket: 'acme-assets',
    publicUrl: 'https://storage.googleapis.com/acme-assets',
  },
  scaling: { minScale: 0, maxScale: 10 },
};

export default config;

Only three things are required: name, registry, and a storage block with provider, bucket and publicUrl.

Substitute your own registry, bucket and publicUrl. The values above are placeholders and will fail — deliberately, rather than appearing to work against something that is not yours.

Private registries

Most registries need credentials to pull from — private GHCR, OCIR, ECR and private Docker Hub all do. Name the Kubernetes Secret that holds them:

imagePullSecrets: ['acme-registry'],

Create that Secret once, in the namespace you deploy into:

kubectl create secret docker-registry acme-registry \
  --docker-server=registry.example.com \
  --docker-username='<user>' \
  --docker-password='<token>'

knext writes it onto the app's service account, so every revision — and the node-local image pre-pull, if you enable it — pulls with the same credentials. Leave it out and a private image leaves the first pods stuck in ImagePullBackOff; adding the secret afterwards does not rescue the running revision, so you would have to deploy again. Naming it here is the whole fix.

The one line that matters most

scaling: { minScale: 0, maxScale: 10 }

minScale: 0 is what makes this a scale-to-zero deployment. Change it to 1 and you have an ordinary always-on service — same app, same everything else, completely different cost and latency character.

It is worth knowing that it is a single knob, because it means the choice is reversible. If cold starts turn out to matter more than idle cost for ACME, you change one number and redeploy.

maxScale: 10 caps how far Knative will fan out under load. It is a spend guard as much as a capacity setting.

What you have now

Two config files and a route handler. Nothing has touched a cluster yet — and if you do not have one ready, this is a good place to pause and set that up (Install).

Chapter 4: Your first deploy →

On this page