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.
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-10If 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:
orchestrator:
subjects:
- kind: ServiceAccount
name: b4-app
namespace: b4-appInstall the sandbox infrastructure first:
helm upgrade --install b4-sandbox-infra \
oci://ghcr.io/cacheplane/charts/b4-sandbox-infra \
--namespace b4-app \
--create-namespace \
--values b4-sandbox-infra-values.yamlThe 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:
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-10Use the same namespace and image values for upgrades:
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-10image.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:
helm upgrade b4-app oci://ghcr.io/cacheplane/charts/b4-app \
--namespace b4-app \
--reuse-values \
--set secretName=my-b4-secretsThe 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-appfor the canonical release);sandboxNamespace=b4-sandboxes, which is informational only;automountServiceAccountToken=trueon 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:
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.yamlEdit 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:
orchestrator:
subjects:
- kind: ServiceAccount
name: b4-app
namespace: b4-appInspect the complete file, then apply it. Helm replaces arrays as values, so do not assign a guessed numeric subject index.
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.yamlKeep 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:
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=falseNo 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.