knext

CLI reference

The kn-next CLI — create, validate, deploy, build, doctor, status, rollback, cleanup, db bind, db migrate, gc.

The kn-next binary ships in @getknext/core (npm i @getknext/core), so it is available as npx kn-next in any project that depends on it. It is a small dispatcher around one job: build → push → apply the NextApp CR. Everything on the cluster is reconciled by the operator; the CLI's cluster writes are limited to applying or patching the CR, and its diagnostic commands are strictly read-only.

kn-next create         # scaffold a new knext-ready app (writes files, no cluster changes)
kn-next validate       # check kn-next.config.ts is filled in and valid (no cluster needed)
kn-next doctor         # cluster-prereq preflight (read-only)
kn-next [deploy]       # default — build → push → apply the NextApp CR
kn-next build          # the build + asset-upload steps only, without deploying
kn-next status         # the NextApp's honest conditions (read-only)
kn-next rollback       # pin traffic to a prior Knative Revision
kn-next cleanup        # remove the app from the cluster (deletes its NextApp CR)
kn-next db bind        # bind an existing Postgres Secret to the CR
kn-next db migrate     # apply pending migrations against the writer, once
kn-next gc             # reap old _next/static/<build-id>/ asset prefixes

kn-next --help lists exactly this set, create first.

Strict parsing, plain errors. Every command — including the default deploy — fails loudly on unknown flags, dangling values, and stray positionals: a typo'd --to or --wacth, or a kn-next --namespace prod cleanup with the command in the wrong place, is a hard error rather than a silent fall-through to a different (possibly opposite) action. Those errors print as a short message and exit 1 — no stack traces. --help works on every command and always exits without doing any work, including on destructive ones like cleanup.

Unknown commands are errors, not deploys. A first argument that is not one of the commands above fails with unknown command: <x>, a "did you mean" suggestion, and exit 1 — so kn-next celanup never ships a deployment. The same applies to a command in the wrong slot: kn-next -n prod cleanup fails with unexpected argument: cleanup and tells you the command comes first, rather than deploying to prod. Running the bin with no command (npx @getknext/core) or with flags only (kn-next --skip-build) still runs deploy, which is the advertised default.

preview and loadtest are not bin subcommands; they ship as separate runnable entries inside the package (see Directly runnable entries below).

kn-next create

kn-next create ships in an upcoming release — it is not yet included in any published @getknext/core version. An installed CLI that does not recognize the command is current, not broken; this page documents create ahead of that release.

Scaffolds a new knext-ready Next.js app into the current directory (or the one you name). It writes files and makes no cluster changes, so it is safe to run and inspect before anything is deployed.

Two of the files it writes are the ones people most often miss when wiring knext into an app by hand, and both are silent failures rather than loud ones:

  • src/app/api/health/route.ts — the shallow route Knative's readiness and liveness probes both target. Without it a deployed revision answers 404 there, never reports Ready, and then gets restart-looped by liveness while the application logs show nothing wrong. The generated route is deliberately shallow: no imports, no I/O, no database. See Configure the deploy.
  • the server entry that serves /_next/imagenext/image optimization is handled by knext's own runtime entry rather than by the platform, so an app scaffolded without it serves the original bytes for every image and nothing reports the difference. See Image caching.

create refuses to clobber: an existing file aborts the whole write before anything is touched, unless you pass --force. It also refuses when a config file already present would shadow the one it writes, rather than emitting a config Next would never read.

The generated package.json pins its @getknext/* dependencies at the CLI's own version — the CLI and those packages release together, so a published CLI always scaffolds versions that exist on the registry. If the CLI you ran carries a version that was never published (typically a build from a source checkout), create checks the registry and prints a warning telling you that npm install would fail and what to do instead. The check is best-effort: with no network access, create still scaffolds normally and stays quiet.

kn-next deploy (default)

Loads and validates kn-next.config.ts, runs next build (standalone output), uploads static assets (with object storage configured), builds and pushes the container image, resolves its digest, and applies a digest-pinned NextApp CR. After a successful deploy it also runs a best-effort asset-retention GC — a GC failure never fails a deploy that has already shipped.

With no storage block in the config, the upload and GC steps are skipped and the deploy prints a notice that static assets are served from the image instead — see Starting without object storage.

Placeholders fail fast. Before any build step, deploy refuses config values still carrying a <...> placeholder from the scaffold (like ghcr.io/<your-user>), with a per-field explanation of what the value is for and what to put there — so an unfinished config never costs you a multi-minute build. Values under env are exempt: they are your app's own free-text data, where angle brackets can be perfectly legitimate (an HTML allowlist, a template string). Run kn-next validate to get the same check on its own. And if the build script cannot find next (dependencies not installed yet), deploy tells you to run npm install instead of printing a crash.

FlagNotes
-r, --registryContainer registry (overrides config; env fallback KN_REGISTRY).
-b, --bucketStorage bucket (overrides config; env fallback KN_BUCKET). Requires a storage block in the config — with none, the flag is a hard error rather than a silently-invented storage setup.
-t, --tagImage tag (default: timestamp; env fallback KN_IMAGE_TAG). The tag doubles as the Next.js BUILD_ID — see Skew protection.
-n, --namespaceKubernetes namespace (default default; env fallback KN_NAMESPACE).
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT). Every cluster call this deploy makes is sent to <ctx>, so it never lands on whichever cluster your kubeconfig happens to point at.
--image <ref>Deploy a pre-built image instead of building one. Applies a NextApp CR pointing at <ref> and does no docker build or push (env fallback KN_IMAGE). The ref must be digest-pinned (registry/name@sha256:…) — a tag-only ref is rejected, matching what the operator's admission accepts. Use it when you already have a published image or have no working docker buildx. A pre-built image is the source of truth for both its server and the static assets baked into it, so --image implies --skip-build and --skip-upload: knext does not re-run next build or upload assets, because it cannot know the image's baked BUILD_ID and any freshly-uploaded _next/static/<id>/ prefix would not match what the deployed server serves (see Skew protection). --registry has no effect alongside it (the ref is already fully qualified) and is reported as ignored.
--skip-buildSkip the next build step.
--skip-uploadSkip the asset-upload step (also skips the post-deploy GC).
--skip-image-lockstep-checkFor a custom in-image-build Dockerfile only (see Custom Dockerfiles that rebuild in-image): skip the post-build check that the pushed image's server actually embeds ASSET_PREFIX and matches the deploy's build id. knext logs a warning every time this is used. Skipping it means a genuinely broken ASSET_PREFIX/build-id lock-step (see Skew protection) is no longer caught before the cluster write — use it only when the check misidentifies your Dockerfile's server layout.
--dry-runPrint the NextApp CR without applying it.
-h, --help / -v, --versionHelp / version.

Config validation runs automatically when the config loads — there is no separate validate command.

No kn-next.config.ts in this directory? That is treated as an expected state, not an error: the CLI prints a short note explaining what the file is, points you at kn-next create for a new app, and exits with status 1 — no stack trace.

kn-next build

The build half of deploy, on its own: runs the project's build script (next build, output: 'standalone'), heals the standalone output, and — with object storage configured — uploads static assets to the bucket (with no storage block, the upload is skipped with a notice; assets ship inside the image). It makes no cluster writes.

After compiling the single executable, build boots it once and checks the three things the platform depends on: your health path answers 200, the metrics port serves /metrics, and the process exits cleanly on SIGTERM. Those obligations live in your app's server entry, so an entry that was swapped or broken still compiles — this catches it on your machine instead of on the cluster, and the error names the obligation that is missing.

The smoke boots a binary built for your machine's architecture; the binary that ships in the image is built for Linux and is verified separately when the image is built.

The compile step needs Bun. The CLI runs under plain Node, but compiling the single executable shells out to bun (1.4 or newer) on your PATH. A missing Bun stops the build with install guidance; a Bun that is present but cannot report its version stops the build with that underlying error — the two are never conflated. See the Bun runtime.

FlagNotes
--skip-nextReuse an existing .next/ build instead of running it again.
--skip-smokeDo not boot the compiled executable. For CI that cannot execute it (a foreign-architecture runner). The build prints a loud warning and the artifact ships unverified.
-h, --helpShow the usage help.

kn-next cleanup

Tears down the app named in kn-next.config.ts by issuing exactly one cluster write: kubectl delete nextapp <name> --ignore-not-found. Owned resources (Knative Service, ServiceAccount, PVC) go with it via owner-reference garbage collection, and the operator's finalizer clears that app's object-store prefix and Redis keyspace — scoped strictly to that app.

Because the command is destructive, a stray positional or an unknown flag is an error rather than an ignored argument.

FlagNotes
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT). Cleanup deletes on <ctx>, never on whichever cluster your kubeconfig happens to point at — so kn-next cleanup --context staging cannot tear down production by mistake.
-h, --helpShow the usage help.

kn-next validate

Checks kn-next.config.ts without touching a cluster — it reads your config file and nothing else: no kubectl, no docker, no network. It loads the config, runs every schema check, and flags placeholder values (like ghcr.io/<your-user>) that still need real ones, explaining per field what the value is for and what to put there.

kn-next validate          # exit 0 means: ready for kn-next deploy

Exit code: 0 when the config is complete and valid; 1 with a plain per-field message otherwise. kn-next deploy runs the same checks before its first build step, so you never need to run validate first — it exists so you can iterate on the config without paying for a build.

It takes no options beyond -h/--help.

kn-next doctor

A read-only preflight that checks the cluster prerequisites: the NextApp CRD, operator readiness, the cert-manager-backed admission webhook, the Knative ingress-class against the reconciler that actually serves it, operator-image pullability, Knative Serving itself, and whether your cluster's CNI can enforce the default-on NetworkPolicy the operator writes — on flannel it cannot (the policy is declarative only), and doctor says so plainly rather than guessing; "cannot determine" is reported as its own outcome, never as "enforced".

kn-next doctor            # human-readable checklist
kn-next doctor --json     # structured results for CI

Each check reports PASS, WARN, FAIL, SKIP, or ERROR — and the distinction is deliberate. When a probe itself fails, doctor classifies the failure before mapping it to a result, so a flaky network is never reported as a missing CRD:

ClassMeaningHint printed
not-foundThe API server answered: the resource is absent.Install the missing prerequisite.
networkThe probe never got an answer (refused / TLS / timeout).Check network/VPN and retry.
authCredentials failed (expired token, Unauthorized).Re-authenticate and retry.
forbiddenAuthenticated but RBAC denied the read.Ask a cluster admin for get/list on the resource.

No cluster yet? Doctor says so plainly instead of blaming the network. Before printing the network hint it looks at your local kubeconfig: a missing kubeconfig file, a config with no current-context, or a connection refused on a local-only address (127.0.0.1, localhost, 0.0.0.0 — usually a leftover from a local cluster that is no longer running) is reported as "you don't have a Kubernetes cluster connected yet", with a pointer to Getting started. The "check network/VPN and retry" hint is reserved for a real, remote cluster that stopped answering.

Exit code: 1 on any hard FAIL or probe ERROR (the cluster state could not be verified); WARN/SKIP never fail. A fully unreachable cluster degrades to all-SKIP and exits 0 — doctor reports what it could verify, it does not guess.

kn-next status

Renders the operator-reported truth from the NextApp CR — URL, image, and the conditions Ready, Degraded, Reconciling, and DatabaseReady — with the operator's reason and guidance verbatim when something is wrong (e.g. IngressNotProgrammed, PinnedRevisionNotFound). Conditions an older operator does not report render as not reported.

kn-next status acme                 # one-shot
kn-next status acme --json          # structured subset (absent conditions are explicit nulls)
kn-next deploy && kn-next status --watch  # CI gate: poll until Ready=True
FlagNotes
<app>Positional, optional — defaults to name from kn-next.config.ts.
-n, --namespaceNamespace (default default).
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT). Reads the NextApp CR on <ctx>, never on whichever cluster your kubeconfig happens to point at.
--jsonOne-shot only — rejected in combination with --watch (a 5s poll would emit concatenated JSON documents; poll status --json from your script instead).
--watchPoll every 5s until Ready=True. Bounded at 10 minutes (exit 1 on timeout); tolerates up to 3 consecutive transient kubectl failures.

Exit code: 1 iff the Ready condition is present with status False — a missing condition is "not reported", not a failure. This makes kn-next deploy && kn-next status --watch a usable CI gate.

kn-next rollback

Shifts serving traffic to a prior Knative Revision by patching only the NextApp CR's spec.traffic (one kubectl merge-patch); the operator renders the Knative traffic split. See Rollback & traffic split for the full semantics.

kn-next rollback acme --to acme-00002              # pin 100% to a prior revision
kn-next rollback acme --to acme-00002 --canary 20  # 20% to latest-ready, 80% pinned
kn-next rollback acme                                    # clear the pin — back to latest-ready
FlagNotes
--to <revision>Prior Knative Revision to pin. Omit to clear any pin.
--canary <n>Integer 1–99 only: percent sent to latest-ready; the remainder goes to the pinned revision. 0 and 100 are rejected (omit the flag, or don't pin). Requires --to.
-n, --namespaceNamespace (default default).
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT).

Because the bare form (kn-next rollback <app>) clears the pin, argument parsing is deliberately strict: a dangling --to, an unknown flag, or an extra positional is a hard error — never a silent un-pin when you asked to pin.

kn-next db bind

Binds an existing Postgres Secret to the app as DATABASE_URL (and optionally a read-only DSN as DATABASE_URL_RO) by setting spec.database.secretRef on the NextApp CR — exactly one cluster write. The operator wires the env injection. See Operator & the NextApp CRD → Databases.

kn-next db bind acme --secret acme-db
kn-next db bind acme --secret acme-db --ro-secret acme-db   # same Secret, both keys
kn-next db bind acme --secret acme-db --dry-run                   # print the patch, write nothing
FlagNotes
--secret <name>Required. Secret carrying the DATABASE_URL DSN.
--key <key>Key inside --secret (default DATABASE_URL).
--ro-secret <name>Secret carrying a read-only DSN → DATABASE_URL_RO.
--ro-key <key>Key inside --ro-secret (default DATABASE_URL_RO).
-n, --namespaceNamespace (default default).
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT).
--dry-runPrint the CR merge-patch YAML without applying it.
--dsn <dsn> / --secret-file <path>Local-only inputs for the connection-contract check (never sent to the cluster).

The command validates the binding against the live CR first (spec.database owns DATABASE_URL, so a conflicting envMap entry is rejected), and after patching it re-reads the CR — on a cluster whose operator predates the spec.database schema the field would be silently pruned, so the CLI fails loudly and tells you to upgrade the operator bundle instead of logging a false success.

kn-next db migrate

Applies pending drizzle-kit-generated migrations against the writer, once, out of the request path.

kn-next db migrate                       # apply ./drizzle against DATABASE_URL
kn-next db migrate --dir ./migrations    # custom migrations directory
kn-next db migrate --url "$WRITER_DSN"   # explicit writer DSN override
FlagNotes
--url <dsn>Writer DSN override (default: DATABASE_URL).
--dir <path> / --migrations <path>Migrations directory (default ./drizzle).
  • Writer-only. It refuses a read-replica DSN — running migrations on a replica is always a bug.
  • Idempotent, fail-loud. Applied migrations are recorded, so a re-run is a no-op; a migration error exits non-zero so a CI step or Job fails instead of shipping a half-applied schema.
  • Run it as a CI step or a one-shot Kubernetes Job — not on pod boot. The Data SDK page has the full Job recipe.

kn-next gc

Reaps old _next/static/<build-id>/ asset prefixes from the object store under the skew-protection retention rule: keep the newest storage.assetRetention build-ids (default 3) plus every build-id currently serving traffic (resolved read-only from NextApp.status.currentTraffic). The same logic runs automatically after every deploy; the standalone command exists for scheduled or manual runs.

kn-next gc                          # prune using the config's app + retention
kn-next gc --build-id 20260711-1    # treat this build-id as the newest
kn-next gc --dry-run                # print the full reap/keep plan, delete nothing
FlagNotes
--build-id <id>Build-id to treat as the newest (e.g. a tag just deployed).
-n, --namespaceNamespace of the NextApp (default default).
--context <ctx>kubectl context to target (default: your current-context; env fallback KN_CONTEXT).
--dry-runCompute and print the full reap/keep plan; issue zero deletes. Composes with --build-id and -n.
-h, --helpShow the usage help.

With no storage block there is nothing to reap: kn-next gc says no object storage configured — nothing to reap and exits 0.

Fail-safe: over-keep, never over-delete. If any live revision cannot be resolved to a build-id, the GC skips entirely — the only possible failure mode is keeping assets too long. Shared, content-hashed directories (chunks/, css/, media/) are never treated as build-id prefixes, and the bare <app>/ prefix is never a prune target. See Skew protection.

Directly runnable entries

Some entries ship in the package as directly runnable scripts rather than bin subcommands:

  • preview (dist/cli/preview.js) — deploy/destroy per-PR ephemeral preview environments (preview deploy --pr <n> --branch <ref> / preview destroy --pr <n>).
  • loadtest (dist/cli/loadtest.js) — generate and apply an ephemeral k6 load-test Job against a deployed service. An operability tool, not part of the deploy path. Accepts --url, --type, -n/--namespace, and --context <ctx> (kubectl context to target; default: your current-context; env fallback KN_CONTEXT). The k6 Job is applied to <ctx>, never to whichever cluster your kubeconfig happens to point at.
node node_modules/@getknext/core/dist/cli/preview.js deploy --pr 42 --branch my-branch
node node_modules/@getknext/core/dist/cli/loadtest.js --type smoke
node node_modules/@getknext/core/dist/cli/loadtest.js --type smoke --context staging

build and cleanup also still ship as dist/cli/build.js / dist/cli/cleanup.js, but prefer the kn-next build / kn-next cleanup subcommands above.

On this page