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 prefixesknext --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/imageoptimization 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.
| Question | Choices | Default | What changes |
|---|---|---|---|
| Runtime | bun, node | bun | node 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. |
| Builder | turbopack, webpack, vinext (Beta) | turbopack | webpack sets the build script to next build --webpack and writes build: "webpack"; vinext scaffolds the compiled single-executable shape. |
| ISR / data cache | none (in memory, per pod), redis | none | redis writes a cache block that reads REDIS_URL. |
| Object storage | none, gcs, s3, minio, azure | none | Writes a storage block for that provider, with <placeholders> for you to fill in. |
| React Compiler | yes / no | yes | On 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:
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.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| Flag | Notes |
|---|---|
--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-compiler | Turn React Compiler on or off. Default on, for every builder. Passing both is an error. |
-y, --yes | Ask nothing; use the defaults for every choice not given as a flag. |
--force | Overwrite existing files. |
--dry-run | List the files that would be written and write nothing. |
-h, --help | Show 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 gitlabBoth 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.
| Flag | Notes |
|---|---|
--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 .). |
--force | Overwrite 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, --help | Show 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| Flag | Notes |
|---|---|
--namespace <name> | Required. The namespace the credential is scoped to. |
--kubeconfig <path> | Kubeconfig file to check. Defaults to $KUBECONFIG. |
-h, --help | Show 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| Flag | Notes |
|---|---|
--check | Report 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, --help | Show 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.
| Flag | Notes |
|---|---|
-r, --registry | Container registry (overrides config; env fallback KN_REGISTRY). |
-b, --bucket | Storage 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, --tag | Image tag (default: timestamp; env fallback KN_IMAGE_TAG). The tag doubles as the Next.js BUILD_ID — see Skew protection. |
-n, --namespace | Kubernetes 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-build | Skip the next build step. |
--skip-upload | Skip the asset-upload step (also skips the post-deploy GC). |
--skip-image-lockstep-check | For 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-run | Print the NextApp CR without applying it. |
-h, --help / -v, --version | Help / 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:
knext deploy --dry-run | kubectl apply -f -
knext deploy --dry-run > nextapp.yaml 2> deploy.logknext 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.
| Flag | Notes |
|---|---|
--skip-next | Reuse an existing .next/ build instead of running it again. |
--skip-smoke | Do 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. |
--verbose | List 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-contained | Opt 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, --help | Show 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.
| Flag | Notes |
|---|---|
--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, --help | Show 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 deployExit 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
NextAppCRD defines every field this CLI can emit. An older CRD than your CLI is reported here;knext deployalso dry-runs the apply on the server before touching the cluster. - kubectl strict validation — a local check that your
kubectlis v1.25 or newer, becauseknext deployapplies the CR with--validate=strictso an unknown field is rejected instead of silently dropped. - Metrics port — whether Knative's queue-proxy binds
:9091on your cluster, which matters only if you overrideMETRICS_PORT(the default is9464). - 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 (anexec:orauth-provider:entry), with the same messageinit-ci --push-secretand 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'withruntime: 'node', itsknext-node-entry.mjswas written once byknext createand is never re-rendered by later builds or deploys. When a newer@getknext/corerelease changes that entry's runtime behavior (for example, adding the deployedCache-Controlnormalization), 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 yourknext-node-entry.mjsagainst the one shipped in the installed@getknext/corepackage andWARNs with the exact fix when they differ: re-scaffold into a throwaway directory withknext create --force <scratch-dir>and copy the freshknext-node-entry.mjsover 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, reportSKIP. - vinext version — knext bundles fixes for vinext that were checked against one exact vinext
version. If your app has a different vinext installed,
knext buildskips every one of those fixes. This check compares the vinext in yournode_moduleswith that version. An older vinext is aFAIL, because the fixes are certainly missing (including the Nitro image optimizer and the 404 for unmatched asset requests); a newer one is aWARN, because that release may already include them. Both name the fix: install the version knext expects and runknext vinext-patches. Apps without vinext get no row, andKNEXT_VINEXT_PATCHES=0reportsSKIP. - 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 aWARN, not aPASS, 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
FAILthat 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 CIEach 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:
| Class | Meaning | Hint printed |
|---|---|---|
| not-found | The API server answered: the resource is absent. | Install the missing prerequisite. |
| network | The probe never got an answer (refused / TLS / timeout). | Check network/VPN and retry. |
| auth | Credentials failed (expired token, Unauthorized). | Re-authenticate and retry. |
| forbidden | Authenticated 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 asNo 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 --verboseshows 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 inupgrade the operator first (operator, then CLI)when the CRD only serves older versions, orupgrade the CLI to match the operatorwhen 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 whenknext deploybuilds the image; upgrade the operator to use it, orspec.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/
imagePullSecretsfix 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| Flag | Notes |
|---|---|
<app> | Positional, optional — defaults to name from knext.config.ts. |
-n, --namespace | Namespace (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. |
--json | One-shot only — rejected in combination with --watch (a 5s poll would emit concatenated JSON documents; poll status --json from your script instead). |
--watch | Poll 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| Flag | Notes |
|---|---|
--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, --namespace | Namespace (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| Flag | Notes |
|---|---|
--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, --namespace | Namespace (default default). |
--context <ctx> | kubectl context to target (default: your current-context; env fallback KN_CONTEXT). |
--dry-run | Print 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| Flag | Notes |
|---|---|
--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| Flag | Notes |
|---|---|
--build-id <id> | Build-id to treat as the newest (e.g. a tag just deployed). |
-n, --namespace | Namespace of the NextApp (default default). |
--context <ctx> | kubectl context to target (default: your current-context; env fallback KN_CONTEXT). |
--dry-run | Compute and print the full reap/keep plan; issue zero deletes. Composes with --build-id and -n. |
-h, --help | Show 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 fallbackKN_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 stagingExit 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.
Related
- Getting started — the deploy walkthrough.
- Operator & the NextApp CRD — what the operator does with the CR.
- Data SDK —
@getknext/db, migrations, and the database binding.
Installing knext (cluster prerequisites)
Cluster prerequisites and the one-command operator install bundle.
Migrate from Vercel
Move a Next.js app from Vercel to your own Kubernetes cluster with knext. A feature map with honest gaps, a step-by-step migration, and a cost worksheet you fill in with your own numbers.