Kubernetes

Deploy Kubernetes applications by building the Node target image first, then installing the b4-app chart. The chart runs an image; it does not build one or translate b4.config.ts.

Prerequisites

Build and publish a Node image as described in Node and Docker:

Confirm that the app has the secret-safe .dockerignore from that guide before running docker build; keep .b4/build in the context.

bash
b4 check
b4 build
docker build -t ghcr.io/you/my-b4-app:2026-08-10 .
docker push ghcr.io/you/my-b4-app:2026-08-10

If the app configures kubernetesSandbox, keep the infrastructure Helm release, the B4.run app, and application credentials in the separate b4-app management namespace while sandbox resources live in b4-sandboxes. Before the first install, prepare the complete intended cross-namespace subject list:

b4-sandbox-infra-values.yaml
orchestrator:
  subjects:
    - kind: ServiceAccount
      name: b4-app
      namespace: b4-app

Install the sandbox infrastructure first:

bash
helm upgrade --install b4-sandbox-infra \
  oci://ghcr.io/cacheplane/charts/b4-sandbox-infra \
  --namespace b4-app \
  --create-namespace \
  --values b4-sandbox-infra-values.yaml

The application chart does not replace the sandbox infrastructure chart. See Kubernetes Sandbox for the provider and cluster-side security boundary.

Install or upgrade the application

After the infrastructure RoleBinding contains the app ServiceAccount, install the app in the b4-app management namespace:

bash
helm install b4-app oci://ghcr.io/cacheplane/charts/b4-app \
  --namespace b4-app \
  --set image.repository=ghcr.io/you/my-b4-app \
  --set image.tag=2026-08-10

Use the same namespace and image values for upgrades:

bash
helm upgrade b4-app oci://ghcr.io/cacheplane/charts/b4-app \
  --namespace b4-app \
  --set image.repository=ghcr.io/you/my-b4-app \
  --set image.tag=2026-08-10

image.repository is required. Pin an immutable tag or set image.digest in production.

Health probes

The chart sends startup, readiness, and liveness HTTP probes to healthPath, which defaults to /healthz. B4.run's response is process liveness, not dependency readiness: it does not query the model, Postgres, or the sandbox provider.

Some store construction happens while the runtime is assembled, so a boot failure can prevent the listener from starting. In fetch/Hono compositions, a request-store factory runs before route dispatch and can even fail a health request. Neither behavior turns /healthz into an active dependency test. Add a separately owned dependency-readiness check if rollout safety requires one.

Environment and secrets

Set ordinary environment entries through env/envFrom. For an existing Kubernetes Secret, use the convenience value:

bash
helm upgrade b4-app oci://ghcr.io/cacheplane/charts/b4-app \
  --namespace b4-app \
  --reuse-values \
  --set secretName=my-b4-secrets

The chart references the Secret but does not create it. Put model credentials and DATABASE_URL in an operator-managed Secret, and apply the tenant and authentication boundary described in Security Architecture.

Shared Postgres stores and local .b4 files have different lifetimes. Configure each state domain with Persistence and Tenancy before relying on restarts or replicas.

Filesystem durability

The b4-app chart mounts only an emptyDir at /tmp. It does not mount the app's .b4 directory. Local SQLite checkpoints, threads, permissions, and other .b4 data are therefore ephemeral across Pod replacement.

Use external durable stores where replacement must preserve state. Sandbox-provider workspaces have their own volume lifecycle and are not made durable by the app Pod's /tmp mount.

ServiceAccount ownership

The chart defaults are:

  • serviceAccount.create=true;
  • serviceAccount.name="", which resolves to the release-scoped chart fullname (b4-app for the canonical release);
  • sandboxNamespace=b4-sandboxes, which is informational only;
  • automountServiceAccountToken=true on the app Pod.

For an app that uses kubernetesSandbox, use the default application-owned ServiceAccount in the management namespace. Add that planned ServiceAccount as a cross-namespace subject of the sandbox infrastructure chart's orchestrator Role before installing the application. A RoleBinding subject is a name reference, so it may refer to a future ServiceAccount. Recording that authorization first prevents a Ready-but-sandbox-broken window in which the app Pod is running but sandbox API calls are still forbidden.

First capture the installed infrastructure chart version and export the release's effective values, including every existing RoleBinding subject:

bash
INFRA_CHART_VERSION="$(helm get metadata b4-sandbox-infra --namespace b4-app | awk '$1 == "VERSION:" { print $2 }')"
test -n "$INFRA_CHART_VERSION" || { printf '%s\n' "unable to determine installed infrastructure chart version" >&2; exit 1; }
helm get values b4-sandbox-infra --all --output yaml \
  --namespace b4-app \
  > b4-sandbox-infra-rbac-values.yaml

Edit b4-sandbox-infra-rbac-values.yaml so orchestrator.subjects contains the complete intended subject list: preserve every existing entry and append the planned application ServiceAccount as a cross-namespace subject. For a release whose existing extra-subject list is empty, that section is:

b4-sandbox-infra-rbac-values.yaml
orchestrator:
  subjects:
    - kind: ServiceAccount
      name: b4-app
      namespace: b4-app

Inspect the complete file, then apply it. Helm replaces arrays as values, so do not assign a guessed numeric subject index.

bash
helm upgrade b4-sandbox-infra oci://ghcr.io/cacheplane/charts/b4-sandbox-infra \
  --version "$INFRA_CHART_VERSION" \
  --namespace b4-app \
  --values b4-sandbox-infra-rbac-values.yaml

Keep the provider's configured namespace, the infrastructure chart namespace, and the informational sandboxNamespace value aligned. Changing sandboxNamespace alone does not change RBAC or where sandbox Pods are created.

Applications that do not configure kubernetesSandbox do not need Kubernetes API credentials or an orchestrator RoleBinding. Install with an application-owned ServiceAccount and disable token mounting as one complete mode:

bash
helm upgrade --install b4-app oci://ghcr.io/cacheplane/charts/b4-app \
  --namespace my-app --create-namespace \
  --set image.repository=ghcr.io/you/my-b4-app \
  --set image.tag=2026-08-10 \
  --set automountServiceAccountToken=false

No orchestrator RoleBinding is needed in this mode because the application does not call the Kubernetes API through kubernetesSandbox. If the application needs some unrelated Kubernetes API permission, grant that separately rather than binding the sandbox orchestrator Role.

Replicas and run coordination

Postgres can provide shared durable stores for checkpoints, thread metadata, and permission decisions. Long-term memory is configured separately. Even with all of those shared, the active one-run-per-thread gate and cancellation registry remain process-local.

More than one replica therefore requires guaranteed thread-aware routing to one owning process, or distributed per-thread serialization plus cancel routing. An HPA changes replica count and a PodDisruptionBudget constrains disruption; neither coordinates B4.run runs or ensures that a cancel request reaches the owning process. Keep the conservative one-replica default until that coordination exists.

See Production Topology for request routing, cancellation, and failure-mode design.

Rollouts and shutdown

The generated Node server.mjs does not opt into serveRuntime signal handlers. The runtime has a drainable close() path, but the generated entry does not currently call it on SIGTERM. Set realistic termination grace periods, test streaming requests through a rollout, and use a caller-owned Node entry when coordinated signal handling is required.

Chart reference

Review the released chart's README.md and values.yaml before installation. The chart exposes image, probe, ServiceAccount, environment, ingress, autoscaling, disruption-budget, and security-context values without enforcing B4.run's higher-level replica or persistence requirements.