knext

Platform defaults

Cluster-wide defaults for resources, timeouts, concurrency and the database connection budget, set once by the cluster admin and inherited by every app that leaves the field unset.

A KnextPlatform is a single, cluster-wide configuration object. It lets the person who runs the cluster set the defaults that every app inherits, without editing each app. An app that sets a field itself always keeps its own value.

Absent means unchanged. Until you create a KnextPlatform, nothing about your apps differs from a cluster that has never heard of it. The install bundle ships the custom resource definition but no KnextPlatform object, so upgrading the operator changes no app. An empty KnextPlatform (spec: {}) and profile: default change nothing either.

What it sets today

The platform can supply a default for these fields. Every field is optional, and a field you leave out keeps its built-in value.

AreaFieldBuilt-in valueApplies to an app that leaves this unset
Resourcesresources.defaults.cpuRequest250mspec.resources.cpuRequest
resources.defaults.cpuLimit1000mspec.resources.cpuLimit
resources.defaults.memoryRequest512Mispec.resources.memoryRequest
resources.defaults.memoryLimit1Gispec.resources.memoryLimit
Requestslimits.timeoutSeconds300spec.timeoutSeconds
Scalingscaling.defaults.containerConcurrency20spec.scaling.containerConcurrency
scaling.defaults.scaleDownDelayKnative's own defaultspec.scaling.scaleDownDelay
scaling.defaults.targetBurstCapacityKnative's own defaultspec.scaling.targetBurstCapacity
scaling.defaults.panicWindowPercentageKnative's own defaultspec.scaling.panicWindowPercentage
scaling.defaults.panicThresholdPercentageKnative's own defaultspec.scaling.panicThresholdPercentage
Databasedatabase.connectionBudget80the cap that maxScale × poolMax must fit within
Rolloutrollout.maxAppsPerMinute10how fast a platform change reaches your apps

It does not choose your runtime (Node or Bun stays a per-app choice), change any Knative configuration, or create a database, cache or ingress.

Create one

The object is cluster-scoped and must be named default. Any other name is rejected.

platform.yaml
apiVersion: platform.kn-next.dev/v1alpha1
kind: KnextPlatform
metadata:
  name: default
spec:
  resources:
    defaults:
      cpuLimit: "2"
      memoryLimit: 2Gi
  limits:
    timeoutSeconds: 600
kubectl apply -f platform.yaml

Apply it with kubectl or through GitOps. The knext CLI never writes it.

Who wins

For every field, per app:

  1. the value on the app, if it set one;
  2. otherwise the platform's value, if it set one;
  3. otherwise the built-in value.

The merge happens when the operator renders the app. Your NextApp is never rewritten, so GitOps diffs stay clean, and a platform edit reaches every app without touching any of them. Resources merge field by field: an app that sets cpuLimit still inherits the platform's memoryLimit.

A preview deployment never keeps a scale-down delay, so it ignores scaling.defaults.scaleDownDelay just as it ignores its own.

One caveat for resources. If an app's knext.config.ts sets any one of scaling.cpuRequest, scaling.cpuLimit, scaling.memoryRequest or scaling.memoryLimit, the CLI fills in the other three with its own defaults, and those count as set by the app. To let the platform supply the rest, set none of the four in knext.config.ts.

See what an app inherited

kubectl get nextapp my-app -o jsonpath='{.status.platform}'
kubectl get nextapp my-app -o jsonpath='{.status.conditions[?(@.type=="PlatformDefaultsApplied")]}'

status.platform records the platform generation and profile the app was last rendered against and the exact fields it inherited, for example spec.resources.memoryLimit. It is absent while no platform is in force. The PlatformDefaultsApplied condition says what happened:

ReasonMeaning
InheritedThe platform supplied at least one value this app left unset.
NothingToInheritA platform is in force but supplies nothing this app leaves unset.
NoPlatformThere is no KnextPlatform named default. Built-in values apply.
NoPlatformCRDThe KnextPlatform definition is not installed. Built-in values apply.
PlatformNotAcceptedThe object failed the operator's own validation and is ignored.
EffectiveSpecInvalidThe platform's value would make this app invalid. The app's current service is held (see below).
RolloutPendingA platform change is queued for this app (see below).

The platform reports on itself too: Accepted, DefaultsPropagated and Ready, plus how many apps are applied, pending and held.

kubectl get knextplatform default -o yaml

What happens when you change it

Only apps whose values actually change get a new revision. Each changed service is a new revision, and a new revision starts a pod. An edit that changes nothing for an app, because it sets that field itself, creates no new revision for it.

Changes are paced. The operator re-renders affected apps at rollout.maxAppsPerMinute (default 10), one every 60 ÷ N seconds, so one edit cannot start a pod for every app at once. A queued app shows RolloutPending until its turn. Your own deploy is never queued behind this: if you change the app itself, it rolls immediately with the platform's values included.

A value that would break an app is held, not applied. If a platform change makes an app's combined settings invalid, for example a lowered connection budget that its maxScale × poolMax no longer fits, or a CPU request above the limit, the operator leaves that app's running service exactly as it is, reports EffectiveSpecInvalid naming the field, and raises a warning event. Fix the app or the platform and the next reconcile applies it. An app that is invalid on its own is rejected as it always was.

If the change being held is one you made to the app, for example a new image, the old version keeps serving, so the app reports Ready: False with the same reason, and kubectl wait --for=condition=Ready does not succeed until the change is applied. A platform change on its own, with the app untouched, leaves Ready as the running service reports it.

Deleting the platform reverts every app to the built-in values, at the built-in pace of 10 apps per minute.

The connection budget

database.connectionBudget replaces the built-in 80 that maxScale × poolMax must fit within for an app that declares a poolMax. The check at kubectl apply time and the check inside the operator use the same number. It creates no database, pooler or connection of its own.

Lowering the budget does not roll out changes to an app that is already over it. There is no grace for an app that was valid before: once its maxScale × poolMax is above the new budget, the operator holds every change to that app and keeps its running service as it is. The change to the app itself is still accepted at kubectl apply time, so deleting the app, editing its labels or annotations, and a deploy that only changes the image all go through, but a new image does not start serving until the app fits. An update that raises maxScale × poolMax above the budget is rejected, and the rejection names the budget.

To get unstuck, do either of these:

  • raise database.connectionBudget back to a value the app fits within; or
  • lower the app's maxScale or poolMax in knext.config.ts until the product fits, then deploy. That change is accepted because it fits.

Before lowering the budget, list what would be over it:

kubectl get nextapp -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,MAXSCALE:.spec.scaling.maxScale,POOLMAX:.spec.scaling.poolMax'

A blank column means that field is unset on the app.

Observability

A held app keeps serving its old version, so nothing about it looks broken from the outside. The operator exports fleet-level metrics for the platform layer on its existing metrics endpoint, so you can alert on it without listing every app.

MetricTypeWhat it tells you
knext_platform_apps_heldgauge, label reasonHow many apps are held right now. reason is EffectiveSpecInvalid or RolloutPending.
knext_platform_holds_totalcounter, label reasonHow many times an app entered a hold. A hold that lasts many reconciles counts once.
knext_platform_rollout_pendinggaugeRe-renders waiting for their turn behind rollout.maxAppsPerMinute.
knext_platform_rollout_wait_secondshistogramHow long a queued re-render was told to wait for its turn. Re-renders that go straight through are not observed.
knext_platform_budget_fallback_totalcounter, label causeAdmissions that ignored the platform's connectionBudget and used the built-in 80. cause is read_error (the platform could not be read) or platform_invalid (it exists but is not valid).

The fallback counter only counts the abnormal paths. A cluster with no KnextPlatform, or one that sets no connectionBudget, uses the built-in value by design and is not counted, so any increase is worth a look: the budget you set is not the one being enforced at kubectl apply.

Two alerts that cover the quiet failures:

# An app has been held for a while: a platform change is not reaching it.
- alert: KnextPlatformAppsHeld
  expr: max(knext_platform_apps_held{reason="EffectiveSpecInvalid"}) > 0
  for: 15m

# The admission check stopped honouring the platform's budget.
- alert: KnextBudgetWebhookFallback
  expr: increase(knext_platform_budget_fallback_total[15m]) > 0

The gauges and the queue reflect the operator process that is running now. After an operator restart they refill as each app is reconciled again, and a queue that was in progress is re-spaced from the start, still within maxAppsPerMinute.

Profiles

spec.profile accepts default or fastColdStart.

  • default sets nothing. It is the built-in values.
  • fastColdStart is accepted but sets no value in this release, and no claim is made for it.

Upgrade order

Operator and definition first, then the KnextPlatform, then the CLI. A KnextPlatform applied before its definition exists is rejected. An operator older than this feature ignores the object, and apps return to the built-in values.

If the definition is missing, the operator still starts and behaves as it did before. Install the definition and restart the operator to switch the platform layer on.

On this page