knext

CLI reference

The knext CLI — create, init-ci, validate, deploy, build, doctor, status, rollback, cleanup, db bind, db migrate, gc.

The knext binary ships in @getknext/core (npm i @getknext/core), so it is available as npx @getknext/core 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.

Use npx @getknext/core <command> (or a locally installed knext, once @getknext/core is a project dependency) — never bare npx knext on its own. The unscoped name knext on the public npm registry belongs to an unrelated package, so outside a project with @getknext/core already installed, npx knext runs someone else's code instead of this CLI.

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

knext --help lists exactly this set, create first.

The CLI was renamed; the old command name still works as a deprecated alias, removed in 2.0. See Getting started for the details.

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 knext --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 knext celanup never ships a deployment. The same applies to a command in the wrong slot: knext -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 (knext --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).

knext create

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/image — next/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.

By default, create scaffolds the standalone target: plain next build (Turbopack), output: 'standalone' in next.config.ts, and the official Next.js Deployment Adapter wired through adapterPath — knext build/deploy stage the matching runtime image automatically, so the scaffold ships no Dockerfile for it. Pass --builder vinext instead to scaffold the compiled single-executable target — its own Dockerfile, vite.config.ts, and build: 'vinext' pinned in knext.config.ts, plus a Dockerfile.self-contained reference recipe for the optional --self-contained single-executable build (see Self-contained mode) — the standalone target never scaffolds that file, since --self-contained only applies to the vinext build. See the adapter and the compiled build for the trade-off between the two.

The questions create asks

Run on a terminal with no flags, create asks five questions. Press Enter on each to take the default: the scaffold described above, with React Compiler turned on. At the end it prints the equivalent flags, so you can repeat the same scaffold in CI.

QuestionChoicesDefaultWhat changes
Runtimebun, nodebunnode writes runtime: "node" to knext.config.ts and adds the Node Redis client, ioredis, to the app's dependencies. Bun has a Redis client built in, so a Bun app gets nothing extra.
Builderturbopack, webpack, vinext (Beta)turbopackwebpack sets the build script to next build --webpack and writes build: "webpack"; vinext scaffolds the compiled single-executable shape.
ISR / data cachenone (in memory, per pod), redisnoneredis writes a cache block that reads REDIS_URL.
Object storagenone, gcs, s3, minio, azurenoneWrites a storage block for that provider, with <placeholders> for you to fill in.
React Compileryes / noyesOn turbopack/webpack: writes reactCompiler: true to next.config.ts and adds babel-plugin-react-compiler to devDependencies. On vinext: writes react: { compiler: true } to the vinext plugin in vite.config.ts and adds the four packages it needs. Answer n, or pass --no-react-compiler, to leave it off. See React Compiler.

With a Redis cache, set REDIS_URL when you run knext deploy, and keep the value in a Kubernetes Secret or your CI secrets rather than in the file. knext deploy stops with an error while it is empty. KN_REDIS_URL works too, at knext deploy and knext preview, and overrides cache.url when both are set. With a storage provider, knext deploy names any placeholder you have not filled in.

create never asks when it cannot expect an answer: with any flag, with --yes, when CI is set, or when stdin or stdout is not a terminal, it uses the default for every choice you did not pass. A pipe or a CI job never waits on a question.

When it finishes, create prints the build target and runtime it chose, defaults included, and how to change them:

output
Build target: turbopack (Next.js `next build` with Turbopack)
Runtime: bun (compiled Bun executable)
Pick others when scaffolding with --builder <turbopack|webpack|vinext> and
--runtime <bun|node>, or later with `build` and `runtime` in knext.config.ts.
shell
knext create my-app                    # asks the questions on a terminal; defaults elsewhere
knext create my-app --yes              # defaults, no questions
knext create my-app --runtime node --cache redis   # Node + Redis, no questions
knext create my-app --no-react-compiler            # without React Compiler
knext create my-app --builder vinext   # compiled single-executable target
knext create my-app --dry-run          # list the files it would write, write nothing
FlagNotes
--name <name>App name. Defaults to the directory name.
--runtime <bun|node>Server runtime. Default bun.
--builder <turbopack|webpack|vinext>Build target. Default turbopack, the standalone shape; default is accepted as turbopack. vinext is the compiled single-executable shape, Beta — see Stability tiers.
--cache <none|redis>ISR / data cache. Default none.
--storage <none|gcs|s3|minio|azure>Object storage for static assets. Default none: assets are served from the container image.
--react-compiler, --no-react-compilerTurn React Compiler on or off. Default on, for every builder. Passing both is an error.
-y, --yesAsk nothing; use the defaults for every choice not given as a flag.
--forceOverwrite existing files.
--dry-runList the files that would be written and write nothing.
-h, --helpShow the usage help.

Exit code: 0 on success (including --dry-run and --help); 1 on a usage error (an unrecognized --runtime, --builder, --cache or --storage value, both --react-compiler and --no-react-compiler, an unexpected extra argument, or a cancelled question) or a scaffold failure (for example an invalid app name, or an existing file without --force).

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.

knext init-ci

Sets up push-to-deploy against your cluster. It writes two files and touches no cluster: a deploy pipeline (.github/workflows/ or .gitlab-ci.yml, chosen with --provider) and a manifest with a ServiceAccount, Role, and RoleBinding that grant permission to write one kind of object in one namespace. knext hosts nothing and never holds your credentials. See Deploy from GitHub Actions and Deploy from GitLab CI/CD.

knext init-ci --namespace my-app
# or, for GitLab:
knext init-ci --namespace my-app --provider gitlab

Both providers run the SAME two refusals, in the SAME order, before any cluster-mutating call: the exec/auth-provider kubeconfig refusal, then the credential-scope + hazard spot-check. On GitHub that is the composite action's two steps; on GitLab it is two dedicated pipeline jobs (kubeconfig-check, credential-preflight) that call knext doctor --ci-kubeconfig and knext ci-preflight — the same classifiers, exposed as CLI verbs so the pipeline never re-derives the rules in shell.

It also prints the exact commands to mint a kubeconfig for that ServiceAccount — a kubectl create token, bound and namespace-scoped, plus the kubectl config set-* calls that assemble a standalone kubeconfig file around it. You run these yourself; knext writes nothing to the cluster. If your own kubeconfig references its certificate authority as a file (or skips TLS verification), the commands warn you before writing a kubeconfig with an empty CA.

The minted token expires after one year (--duration=8760h). When it expires, CI can no longer reach the cluster. Before then, re-run the whole printed recipe from the top and push the secret again (for example with --push-secret below) — it removes its own output file first, so the renewed token replaces the old one. Re-running only the last two lines over an existing file refuses instead of silently keeping the old token. Change --duration to match your rotation policy; your cluster may cap it lower. To revoke the token early, delete and recreate the knext-deployer ServiceAccount.

Any kubeconfig that instead needs a cloud CLI on the runner (an exec: or auth-provider: entry — the aws/gcloud/oci/az shape) is refused wherever knext reads one — here, in the GitHub Action, and in knext doctor --ci-kubeconfig — with the same message:

This kubeconfig needs cloud-account credentials on the runner. Use the knext-deployer ServiceAccount token.

Bearer tokens and client certificates are accepted — neither runs anything on the runner.

FlagNotes
--namespace <name>Required. The namespace to deploy into; it bounds what the credential can do.
--provider <name>github (default) or gitlab. Chooses which pipeline file is generated; the RBAC manifest and the mint recipe are identical either way.
--app-dir <path>App directory, relative to the repository root (default .).
--forceOverwrite files that already exist.
--push-secret <path>Read a kubeconfig from <path> and push it as KNEXT_KUBECONFIG — via gh secret set (github) or glab variable set --masked --protected (gitlab). Refuses a kubeconfig that needs cloud-account credentials, same as above. The value is never printed or logged — it is only ever piped to the provider CLI's stdin. With --provider gitlab and no glab on PATH, it prints the manual steps to add the variable in GitLab's UI instead of failing.
-h, --helpShow the usage help.

Exit code: 0 on success (including --help); 1 on a usage error (missing --namespace, an unrecognized --provider) or a failure pushing --push-secret (a missing file, or the provider CLI's push itself failing).

Re-running it without --force never clobbers a file you've edited: it names the exact file and points you at the flag, e.g. .github/workflows/knext-deploy.yml already exists — left alone (use --force to overwrite).

knext ci-preflight

The credential-scope + hazard spot-check that init-ci --provider gitlab's generated pipeline runs before deploy — the same check kn-next-action's composite action runs on GitHub, exposed as a CLI verb so a pipeline with no composite-action equivalent can invoke it through the published CLI instead of re-deriving the rules in shell. Not typically run by hand.

It asks your cluster what the credential can do (SelfSubjectRulesReview) and, because some clusters (OKE, GKE with IAM) cannot fully answer that, also spot-checks specific hazardous permissions (SelfSubjectAccessReview). Every failure mode — the review cannot be reached, returns no verdict, or reports the credential can do more than the published Role — refuses rather than proceeding: a check that passes when it cannot see is not a check.

knext ci-preflight --namespace my-app
FlagNotes
--namespace <name>Required. The namespace the credential is scoped to.
--kubeconfig <path>Kubeconfig file to check. Defaults to $KUBECONFIG.
-h, --helpShow the usage help.

Exit code: 0 (including --help) — prints preflight: credential is correctly scoped for namespace "<ns>". when the credential passes; 1 on a usage error (missing --namespace, no kubeconfig configured) or any refusal, printing the reason — never the kubeconfig's bytes — to stderr.

knext vinext-patches

Applies the vinext fixes knext bundles ahead of upstream vinext releases to the vinext installed for this app, so you do not wait for those fixes to ship upstream. An app created with knext create --builder vinext runs it from its postinstall script, and knext build runs it again before the vinext build, so it is not typically run by hand.

The fixes are tied to one exact vinext version (the one knext create pins). With any other vinext version installed it changes nothing and says so. Re-running it is safe: fixes that are already in place are left alone. If a fix neither applies cleanly nor is already present — vinext's files were modified some other way — it refuses rather than half-applying; reinstall your dependencies and run it again. Applying is all-or-nothing per fix: if writing any of a fix's files fails (for example a read-only node_modules), the files it already changed are restored and the command exits 1.

The postinstall script skips this step quietly when @getknext/core is not installed, as in a production-only install (npm ci --omit=dev), because it is a development dependency.

To turn the bundled fixes off, for example because you patch vinext yourself, set KNEXT_VINEXT_PATCHES=0. Both the postinstall step and knext build then leave vinext untouched and print a line saying the fixes were not applied.

If your app has a vinext version other than the one the fixes were checked against, none of them are applied. By default knext build carries on and prints a warning that says how many fixes were skipped, what the build is missing, and how to fix it (install the expected vinext version and run knext vinext-patches). Set KNEXT_VINEXT_PATCHES=strict to make that mismatch an error instead: knext build and knext vinext-patches then stop with exit code 1 and the same fix. Use it in CI when you want a build with missing fixes to fail rather than ship. knext doctor reports the mismatch too.

knext vinext-patches          # apply
knext vinext-patches --check  # report only, change nothing
FlagNotes
--checkReport whether every fix is applied without changing any file; exit 1 when one is missing.
KNEXT_VINEXT_PATCHES=0 (env)Skip the bundled fixes entirely, here and in knext build.
KNEXT_VINEXT_PATCHES=strict (env)Fail, instead of warning, when the installed vinext is not the version the fixes were checked against.
-h, --helpShow the usage help.

KNEXT_VINEXT_PATCHES accepts 0 (or false, off, no), strict, or unset; the value is not case-sensitive and surrounding spaces are ignored. Any other value, such as a misspelling like stric, is treated as unset: knext vinext-patches, and knext build on the vinext build target, print a warning that names the value and lists the accepted ones, and the default warn behaviour applies. Other build targets do not read the variable.

Exit code: 0 (including --help, when vinext is not installed, and when it is a different version unless KNEXT_VINEXT_PATCHES=strict is set); 1 on a usage error, a fix that conflicts with the installed vinext, a different vinext version under KNEXT_VINEXT_PATCHES=strict, or --check finding a fix not yet applied.

knext deploy (default)

Loads and validates knext.config.ts, runs your build (next build with standalone output on the default target), uploads static assets (with object storage configured), builds and pushes the container image, resolves its digest, and applies a digest-pinned NextApp CR. On the vinext build with your own Dockerfile that rebuilds in-image, the assets are extracted from the pushed image and uploaded from there — see Custom Dockerfiles that rebuild in-image. 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.

After the NextApp CR is applied, deploy waits briefly (up to 15s) for the operator to begin reconciling THIS apply specifically — not a stale status left over from an earlier one — before reporting the URL. That holds on a redeploy too: if the operator isn't running, is CrashLoopBackOff, or is watching the wrong namespace, deploy says so plainly instead of quoting the URL from the last time it actually reconciled: NextApp applied; no operator reconciled it in 15s. Check the operator pod: kubectl get pods -n kn-next-operator-system.

A held change fails the deploy. If the operator observes your change but holds it, because the platform's defaults make the app's effective spec invalid (for example the connection budget is exceeded), the previous version keeps serving and the app reports not Ready. Deploy then exits 1 with the operator's message, which names the field, and the remedy: raise the platform budget, or lower maxScale / poolMax in knext.config.ts, then deploy again.

The same applies when the operator rejects your own spec outright (for example a field that fails validation, or a footprint over the built-in budget): the previous version keeps serving, deploy exits 1 with the operator's message naming the field, and the remedy is to fix the spec in knext.config.ts and deploy again.

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 knext 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, and a placeholder registry in knext.config.ts does not block the deploy (other placeholders, such as the storage bucket, still do).
--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, and the check that every client chunk the image's server references was uploaded (assets are still taken from the image). 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.

Exit code: 0 on success (including --help/--version); 1 on a usage error (an unknown flag, a stray positional), a missing knext.config.ts, or a deploy failure at any step (build, upload, image push, or the CR apply).

Piping --dry-run to kubectl apply -f -. Under --dry-run, stdout carries only the rendered NextApp YAML document; every log line goes to stderr. The output pipes or redirects cleanly:

shell
knext deploy --dry-run | kubectl apply -f -
knext deploy --dry-run > nextapp.yaml 2> deploy.log

knext db bind --dry-run follows the same rule for the patch it prints.

Config validation runs automatically when the config loads. knext validate runs the same checks on their own.

No knext.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 knext create for a new app, and exits with status 1 — no stack trace.

knext build

The build half of deploy, on its own: runs the project's build script (next build with output: 'standalone' on the default target), 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.

On the vinext build (build: 'vinext'), 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 executable for runtime: 'bun' (the default) or the vinext build shells out to bun (1.4 or newer) on your PATH. runtime: 'node' on the standalone targets needs no Bun. 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.
--verboseList every compile and closure note. By default they are folded into one line, such as [knext standalone-compile] 12 notes; rerun with --verbose for details. Warnings and errors always print in full.
--self-containedOpt in to the self-contained single-executable mode, overriding selfContained in knext.config.ts. Experimental: on the vinext target the binary needs nothing beside it; the other targets do not use it yet, so there it only shows up in the build log and the build output is unchanged.
-h, --helpShow the usage help.

Exit code: 0 on success (including --help); 1 on a usage error (an unknown flag), a missing knext.config.ts, or a build/smoke failure.

A Next.js with a known security advisory builds, but not silently. knext build and knext deploy read the Next.js your app resolves — the same way knext doctor does — and print a warning when it is affected by a published critical or high advisory, naming each GHSA id and the version to upgrade to (for example, any Next.js up to 16.4.0 must move to 16.4.1 or later). It is a warning, not a failure: the build and deploy continue, and knext doctor is the check that fails. knext deploy --image deploys an image built elsewhere, so it has no local Next.js to check. See Next.js security upgrades.

knext cleanup

Tears down the app named in knext.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 knext cleanup --context staging cannot tear down production by mistake.
-h, --helpShow the usage help.

Exit code: 0 on success (including --help); 1 on a usage error (any flag or positional other than --context/-h/--help — destructive commands never silently ignore an argument), a missing knext.config.ts, or a failed delete.

knext validate

Checks knext.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.

knext validate          # exit 0 means: ready for knext deploy

Exit code: 0 when the config is complete and valid; 1 with a plain per-field message otherwise. knext 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.

knext 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".

A few checks are local — they read files in the directory you run knext doctor from and never touch the cluster, so they still run even when nothing above is reachable:

  • Static asset mode — states whether object storage is configured for this app, or assets are served from the image.
  • CRD schema coverage — whether the installed NextApp CRD defines every field this CLI can emit. An older CRD than your CLI is reported here; knext deploy also dry-runs the apply on the server before touching the cluster.
  • kubectl strict validation — a local check that your kubectl is v1.25 or newer, because knext deploy applies the CR with --validate=strict so an unknown field is rejected instead of silently dropped.
  • Metrics port — whether Knative's queue-proxy binds :9091 on your cluster, which matters only if you override METRICS_PORT (the default is 9464).
  • App image pullability — whether each NextApp's image can be pulled, and whether a pull credential is attached to the app's ServiceAccount.
  • CI kubeconfig safety (opt-in, --ci-kubeconfig <path>) — checks a kubeconfig FILE, not the cluster: refuses one that needs cloud-account credentials on the runner (an exec: or auth-provider: entry), with the same message init-ci --push-secret and the GitHub Action use. Adds this one row only when the flag is passed.
  • vinext-on-node entry freshness — if your app uses build: 'vinext' with runtime: 'node', its knext-node-entry.mjs was written once by knext create and is never re-rendered by later builds or deploys. When a newer @getknext/core release changes that entry's runtime behavior (for example, adding the deployed Cache-Control normalization), an app scaffolded earlier keeps running its old copy silently — no error, no crash, just the old behavior. This check compares a version marker in your knext-node-entry.mjs against the one shipped in the installed @getknext/core package and WARNs with the exact fix when they differ: re-scaffold into a throwaway directory with knext create --force <scratch-dir> and copy the fresh knext-node-entry.mjs over yours (diff first if you made local edits). Apps that don't use this runtime combination, or that aren't run from their own directory, report SKIP.
  • vinext version — knext bundles fixes for vinext that were checked against one exact vinext version. If your app has a different vinext installed, knext build skips every one of those fixes. This check compares the vinext in your node_modules with that version. An older vinext is a FAIL, because the fixes are certainly missing (including the Nitro image optimizer and the 404 for unmatched asset requests); a newer one is a WARN, because that release may already include them. Both name the fix: install the version knext expects and run knext vinext-patches. Apps without vinext get no row, and KNEXT_VINEXT_PATCHES=0 reports SKIP.
  • Next.js minor — whether the Next.js your app resolves is on a minor the official compatibility suite has credentialed. A supported but uncredentialed minor is a WARN, never a failure. A credentialed minor that is affected by a published security advisory is also a WARN, not a PASS, and the row never suggests moving to a minor that has no release fixing such an advisory — today that means it recommends 16.4.x even though the credential covers 16.3.
  • Next.js security advisories — whether that Next.js is affected by a published critical or high advisory that has a fix. An affected version is a FAIL that names each advisory by its GHSA id and the version to upgrade to (for example, anything up to 16.4.0 must move to 16.4.1 or later — see Next.js security upgrades). Apps with no resolvable Next.js get no row.
knext doctor                                        # human-readable checklist
knext doctor --json                                 # structured results for CI
knext doctor --verbose                              # add the raw kubectl/API diagnostic behind each message
knext doctor --ci-kubeconfig ./deployer.kubeconfig  # check a kubeconfig FILE before wiring it into 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.

One plain sentence, no stack trace. The messages you're most likely to hit before you have a cluster wired up are each a single actionable line by default — no raw kubectl output, no jargon:

  • No cluster configured. Before printing the network hint, doctor looks at your local kubeconfig: a missing kubeconfig file, or one with no current-context, is reported as No Kubernetes cluster configured. See https://knext-platform.dev/docs/first-cluster. — whichever of the two it is, you get the exact same sentence and the same walkthrough link. 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) gets its own, differently-worded message. The "check network/VPN and retry" hint is reserved for a real, remote cluster that stopped answering. knext doctor --verbose shows which of the two states it was and where it looked.
  • NextApp CRD not installed. NextApp CRD not found. Install the operator: kubectl apply --server-side -f <the published install manifest>.
  • CRD version not served. Doctor pairs your CLI with the cluster by CRD API version: the NextApp CRD row reads the versions the installed CRD serves and fails with this CLI emits apps.kn-next.dev/<version>, but the installed nextapps.apps.kn-next.dev does not serve <version> … when yours is not among them, ending in upgrade the operator first (operator, then CLI) when the CRD only serves older versions, or upgrade the CLI to match the operator when it only serves newer ones (both when it is unclear which side is behind). A CRD that serves several versions passes as long as one is yours, deprecated or not; a CRD doctor cannot read fails rather than passing. The Operator version row is informational only — a different operator major is not a warning. See Versioning.
  • Operator behind the CLI. When the installed CRD is missing fields this CLI can emit, doctor says so as operator behind CLI: the installed CRD is missing N field(s) this CLI emits — they would be dropped. Upgrade the operator first (operator, then CLI). — the full field list (for filing a ticket or debugging) is available with --verbose.
  • CRD predates an optional feature. If the only missing fields are ones the CLI emits just for a specific feature, doctor warns instead of failing and names the feature — for example spec.security.writeFree — needed for write-free pods when knext deploy builds the image; upgrade the operator to use it, or spec.networking — needed for private apps. Deploys that don't use those features are unaffected; one that does is refused before anything is uploaded.
  • Registry auth failure. When an app's image can't be pulled anonymously and no credential is attached, doctor names the missing Secret/imagePullSecrets fix directly in the check's line, with a pointer to private registries.

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.

knext 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.

knext status acme                 # one-shot
knext status acme --json          # structured subset (absent conditions are explicit nulls)
knext deploy && knext status --watch  # CI gate: poll until Ready=True
FlagNotes
<app>Positional, optional — defaults to name from knext.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 knext deploy && knext status --watch a usable CI gate.

knext 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.

knext rollback acme --to acme-00002              # pin 100% to a prior revision
knext rollback acme --to acme-00002 --canary 20  # 20% to latest-ready, 80% pinned
knext 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 (knext 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.

Exit code: 0 on success (including --help); 1 on a usage error (a dangling --to, an unknown flag, an extra positional) or a failed patch.

knext 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.

knext db bind acme --secret acme-db
knext db bind acme --secret acme-db --ro-secret acme-db   # same Secret, both keys
knext 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.

Exit code: 0 on success (including --dry-run/--help); 1 on a usage error (an unknown db subcommand, a missing app name, a missing --secret) or a failed patch/verification — including the operator-too-old case above.

knext db migrate

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

knext db migrate                       # apply ./drizzle against DATABASE_URL
knext db migrate --dir ./migrations    # custom migrations directory
knext 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.

Exit code: 0 on success; 1 on a usage error (an unknown flag, an unexpected positional) or a failed migration (fail-loud by design — see above).

knext 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.

knext gc                          # prune using the config's app + retention
knext gc --build-id 20260711-1    # treat this build-id as the newest
knext 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: knext 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.

Exit code: 0 once a run completes — including the no-storage notice above and every fail-safe skip (keeping assets too long is never treated as a failure); 1 on a usage error or a missing knext.config.ts.

Directly runnable entries

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

  • preview (dist/cli/preview.js) — (experimental) deploy/destroy per-PR ephemeral preview environments (preview deploy --pr <n> --branch <ref> / preview destroy --pr <n>). Flags and output may change in a minor release.
  • loadtest (dist/cli/loadtest.js) — (experimental) 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. Flags and output may change in a minor release.
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

Exit codes: both exit 0 on success and 1 on a usage error (a missing --pr/--branch, an unrecognized preview subcommand, a missing --url or an invalid --type for loadtest) or a failed run. Neither accepts -h/--help today — a bare invocation with no recognizable subcommand (preview) or a missing required flag (loadtest) is how you reach the usage message. loadtest does not reject an unrecognized flag (it parses permissively); preview does.

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

On this page