Runtime images and pools¶
Approval and placement are independent. A cluster-scoped RuntimeImage
approves a digest-pinned runtime; a NodeProfile offers IDs on named pools.
Pods select an ID, never a runtime digest.
Prepare a valid golden image¶
An approved ("golden") image need not be custom-built. A compatible upstream OCI image can be approved unchanged, including a shell-free distroless image. The platform must validate that its root:
- Supports every actual architecture of its offering pools.
- Has unique environment names. PATH is optional; when present, its components
are nonempty canonical absolute directories outside
/app. Missing directories are allowed; symlinks must not escape, loop or traverse/app. - Supports the approved nonzero numeric UID/GID. No account named
app, passwd/group database or pre-existing home is required. Present account metadata must be well formed and unambiguous. - Contains no set-ID binaries or file capabilities.
The runtime config contributes only Env. Runtime Entrypoint, Cmd, WorkingDir and User do not define the application process. Brewlet does not build or silently sanitize the approved image.
Use an explicit absolute executable in launch.json, such as
/usr/bin/python3.13 or /nodejs/bin/node, when the runtime does not supply a
suitable PATH. Without a runtime PATH, relative initial/Exec invocation fails
with RuntimePATHRequired; CRI defaults and the working directory are not
fallback search paths. No runtime shell, entrypoint script or account-management
utility is invoked by the provisioner.
Runtime /app contents are hidden, not merged with application layers or
removed from the approved image. A private opaque boundary in the container's
overlay keeps packaged application/dependency bytes isolated and read-only;
explicit Kubernetes volumes still follow that boundary. Runtime and payload
caches remain unchanged.
HOME uses an explicit final environment value when supplied, otherwise optional
passwd metadata for the actual UID, otherwise /tmp. This does not create a
home directory or make NSS lookups work without the relevant database.
Applications requiring username lookup must select a suitable runtime.
Declare data and configuration mount locations¶
Brewlet requires a read-only container root, not just an immutable shared
node cache. A process cannot modify a private copy of the runtime either.
The platform explicitly approves additional volume locations with
spec.volumeMountPaths:
apiVersion: node.brewlet.sh/v1alpha1
kind: RuntimeImage
metadata:
name: python-3.13
spec:
source:
image: registry.example.com/golden/python@sha256:<64-lowercase-hex>
user:
uid: 65532
gid: 65532
volumeMountPaths:
- /cache
- /tmp
This allows workload volumes at those paths or their descendants; it does
not create volumes or make the directories writable automatically. Omit the
list for workloads that need only explicit mounts beneath /app/.
The policy covers read-only configuration mounts as well as writable storage,
so a volume cannot silently replace runtime programs or libraries.
Choose canonical data/configuration directories, not symlinks, runtime PATH directories or their ancestors. Lists are sorted, unique and nonoverlapping, with at most 32 entries. The policy is immutable within a runtime inventory identity; use a new ID for a different mount contract. Standard Kubernetes system mounts and read-only service-account projection are handled separately.
App teams mount their emptyDir, PVC or configuration volumes in those
approved locations and set any runtime-specific cache variables themselves.
For example, a WebAssembly runtime might declare /cache and /work, keeping
engine compilation caches separate from guest-writable storage. See
writable storage for the workload side.
This is a template. Replace the source with your validated golden image digest. The IDs and numeric identities are examples, not a built-in catalog. Brewlet does not guarantee that every upstream image or architecture is compatible; validate each image you approve.
Place IDs on pools¶
apiVersion: node.brewlet.sh/v1alpha1
kind: NodeProfile
metadata:
name: application-pool
spec:
nodePool:
key: brewlet.example.com/pool
names: [applications]
runtimeImages:
- id: python-3.13
install: OnDemand
idleTTL: 72h
rollout:
containerdRestart: validated
maxUnavailable: 1
tolerations:
- key: startup-taint.cluster-autoscaler.kubernetes.io/brewlet
operator: Equal
value: provisioning
effect: NoSchedule
Selecting a pool authorizes privileged host modification. Use only disposable
or explicitly controlled nodes. Control-plane nodes are excluded unless the
profile opts in. spec.tolerations applies to the provisioner worker only, and
there is no default; the example lets the worker run on nodes that still carry
the startup fence.
Installation modes¶
| Mode/state | Meaning |
|---|---|
| Eager (default) | Validate/install the required root before aggregate readiness. |
| OnDemand available | Baseline is validated; the runtime root has not been fetched. |
| OnDemand installing | A freshly authorized first-use request is being processed. |
| Ready | A validated immutable generation is current. |
| Failed | Scheduling capability is withdrawn; installation retries with backoff. |
The shim creates an empty private marker for a cold authorized ID, never pulls the runtime. Its default install wait is at most 30 seconds and remains below a forwarded request deadline; hosts must configure kubelet's runtime request timeout above that default. Failure does not authorize new markers.
idleTTL is permitted only for OnDemand. Reclamation waits until no container
uses the runtime; it does not kill workloads.
Apply RuntimeImage and NodeProfile objects with kubectl, or supply named
profiles through the provisioner.profiles Helm value (see
configuration). With no profiles, Brewlet provisions no
nodes.
Startup fencing and provider labels¶
brewlet.sh/runtime=ready is only usable readiness when the startup fence is
absent. Per-ID capabilities are brewlet.sh/runtime.<id>=available|ready.
Application Pods must never tolerate the provisioning fence.
Provider templates do not prove a real node is ready. OnDemand per-ID labels must be mutable. If a provider pins a stale capability, confirmed node-wide fencing is required; if withdrawal and fencing both fail, progress remains blocked. Cloud scale-from-zero has not been verified.