official next.js deployment adapter · knative

Scale‑to‑zero Next.js, on Knative.

knext is a deployment adapter that runs Next.js on the official Adapter API with true scale-to-zero — pods drop to nothing when idle and wake on the first request, with bytecode-cached cold starts. One operator. Any cloud. No lock-in.

New to Kubernetes? The tutorial starts with the six words you need, then deploys ACME — a small storefront — and gives it a database.

replicas · autoscaler: KPA · target idle → 0
idle cost: $0 · pods scaled to zerocold wake: bytecode cache skips JS recompile
// why knext
01

Official adapter, not a fork

Built on Next.js's official Deployment Adapter API (NextAdapter, output:'standalone'). No reverse-engineered runtime. Validated against the official Next.js deploy-mode e2e suite — 778 tests, zero failures on Node, re-verified nightly. Scope and exclusions: verified compatibility.

02

True scale-to-zero

Knative KPA + activator: idle services drop to 0 replicas and wake on demand through the activator. You pay for requests, not idle containers.

03

Bytecode-cached cold starts

A V8 compile cache is baked into your image at build time — every cold pod skips JS recompilation, with nothing to enable. Or run Bun with build-time bytecode precompilation (−47% measured boot). Self-hosted, on your cluster.

04

Multi-cloud, no lock-in

One Go operator + a NextApp CRD, on any Kubernetes cluster running Knative Serving. Verified end-to-end on GKE, OKE and kind; portable to EKS, AKS and bare-metal by design, not yet certified there. Object storage via gcs, s3, azure or minio. Your manifests are yours.

// declare it, the operator reconciles it

Your app is a resource.

Describe the deployment once as a NextApp custom resource. The operator — the single source of truth for cluster state — builds the Knative Service, wires scale-to-zero, and rejects any image that is not digest-pinned.

  • 01 push a digest-pinned image
  • 02 apply the NextApp CR
  • 03 operator reconciles → reachable URL, scaled to zero
# the whole deployment, declared
apiVersion: apps.kn-next.dev/v1alpha1
kind: NextApp
metadata:
  name: acme
spec:
  image: registry/acme@sha256:9f1c...   # digest-pinned (required)
  scaling:
    minScale: 0      # scale to zero
    maxScale: 20
  # bytecode cache is baked into the image — nothing to configure
// databases — bring your own Postgres

Bring your own database.

knext is engine-agnostic: it provisions nothing and manages no database. You bring your own Postgres — a CloudNativePG cluster in the same cluster, or a managed / serverless provider — and knext binds its DSN from a Kubernetes Secret into your pods as DATABASE_URL. Swapping providers is a Secret change: no app or CR schema change.

  • 01 put your Postgres DSN in a Kubernetes Secret
  • 02 reference it with spec.database.secretRef — the operator wires DATABASE_URL (+ optional roSecretRef DATABASE_URL_RO)
  • 03 query it with typed @getknext/db (Drizzle) — writer / bounded-stale reader split
  • 04 front Postgres with a connection pooler (PgBouncer / pgcat) so scale-to-zero fan-out never storms the database
# bring your own Postgres — bind an existing Secret
apiVersion: apps.kn-next.dev/v1alpha1
kind: NextApp
metadata:
  name: acme
spec:
  image: registry/acme@sha256:9f1c...
  scaling:
    minScale: 0                      # the app scales to zero
  database:
    secretRef: { name: acme-db }   # → DATABASE_URL from a K8s Secret
    roSecretRef: { name: acme-db }  # optional → DATABASE_URL_RO
    # point the DSN at a pooler, not the primary