Installation¶
This page enables Brewlet on a Kubernetes cluster: the operator, the node
provisioner, and the admission webhook. After this, any pod with
runtimeClassName: brewlet runs a Java application (packaged as an OCI artifact) directly on a node JDK.
There are two paths:
- Helm (recommended) — the SpinKube-style single-command activation.
- Manual — apply the raw manifests yourself.
⚠️ Node provisioning is privileged and mutates the host (installs a shim, JDK roots, and edits
/etc/containerd/config.toml). Provision only nodes your platform team controls; on mixed clusters scope with namedNodeProfiles (§5.6) rather than the all-nodes default profile. See Security.
Prerequisites¶
| Requirement | Why |
|---|---|
| Kubernetes with containerd as the CRI runtime | The shim is a containerd Runtime v2 shim. |
| cgroup v2 on nodes | Brewlet requires it; the provisioner refuses cgroup v1-only nodes. |
| Nodes you control | Provisioning is privileged and host-mutating. |
kubectl + helm (for the Helm path) |
To install and manage node provisioning. |
| A reachable OCI registry | Where developers push OCI artifacts (and where component + vendor JDK images live). |
| Node access to JDK images | Vendor JDK/launcher images (Temurin, MS OpenJDK) pulled copy-from-image via the host containerd; mirror them for air-gapped clusters. |
Released components¶
Brewlet publishes version-aligned multi-architecture component images and an OCI
Helm chart. Installing chart 0.1.0 selects image tag 0.1.0 automatically.
Pin to your own registry or immutable digests in production.
To build the components from source instead, use the Kubernetes component Makefile:
git clone https://github.com/brewlet/brewlet.git
cd brewlet
docker buildx build --platform linux/amd64,linux/arm64 \
-t <registry>/operator:<tag> --push kubernetes
docker buildx build --platform linux/amd64,linux/arm64 \
--build-arg CMD=admission -t <registry>/admission:<tag> --push kubernetes
make provisioner-image-push \
PROVISIONER_IMAGE=<registry>/node-provisioner:<tag>
These commands build multi-arch (linux/amd64,linux/arm64) images via buildx
and require a logged-in registry. The provisioner image compiles the shim
inside the build for each target arch, so the installed shim always matches
the node.
Helm (recommended)¶
The charts/brewlet chart installs the operator, the
provisioner RBAC, and the admission webhook. The operator then creates and
reconciles the provisioner DaemonSet and the brewlet RuntimeClass from the chart's
values — so there is a single runtime source of truth for the JDK/launcher inventory.
helm upgrade --install brewlet oci://ghcr.io/brewlet/charts/brewlet \
--version 0.1.0 \
--namespace brewlet \
--create-namespace \
--set provisioner.jdks="temurin-21,microsoft-25" \
--set provisioner.launchers="jaz"
# The chart renders a default NodeProfile that provisions EVERY node (§5.6) —
# there is no per-node opt-in step. The operator provisions each node and the
# provisioner marks it ready once the shim + JDK + runtime are installed. Watch:
kubectl get nodes -L brewlet.sh/runtime -w
To limit provisioning to platform-owned pools instead of every node, disable the chart's default profile (
--set defaultProfile.enabled=false) and define namedNodeProfiles scoped to those pools — see Configuration (profiles/defaultProfile) and SPECIFICATION §5.6.
Point the chart at your own registry or image digests if required:
helm upgrade --install brewlet oci://ghcr.io/brewlet/charts/brewlet \
--version 0.1.0 \
--namespace brewlet \
--create-namespace \
--set images.operator=<registry>/operator:<tag> \
--set images.provisioner=<registry>/node-provisioner:<tag> \
--set images.admission=<registry>/admission:<tag> \
--set provisioner.jdks="temurin-21"
Every value is documented in Configuration. Lint / preview the rendered manifests before installing:
Upgrading¶
Helm does not upgrade CRDs placed under a chart's crds/ directory. Before
upgrading an existing Brewlet installation to a release that adds custom JDK or
jlink runtime sources, apply that release's NodeProfile CRD explicitly:
kubectl apply -f https://raw.githubusercontent.com/brewlet/brewlet/v0.1.0/kubernetes/deploy/nodeprofile-crd.yaml
helm upgrade brewlet oci://ghcr.io/brewlet/charts/brewlet \
--version 0.1.0 \
-f values.yaml
What the chart deploys vs. what the operator creates¶
| Deployed by the chart | Created/reconciled by the operator at runtime |
|---|---|
brewlet-operator Deployment + RBAC |
brewlet-node-provisioner DaemonSet |
Node-provisioner ServiceAccount + ClusterRole |
The brewlet RuntimeClass |
brewlet-admission webhook + serving cert |
(tracks node readiness, emits events) |
Manual (without Helm)¶
If you'd rather not use Helm, apply the raw manifests and run the operator directly.
# 1. Namespace + provisioner ServiceAccount/RBAC (and, if you want to hand-wire it,
# the provisioner DaemonSet):
kubectl apply -f kubernetes/deploy/node-provisioner.yaml
# 2. The operator ServiceAccount + RBAC + Deployment:
kubectl apply -f kubernetes/config/operator.yaml
# 3. Opt nodes in. The standalone provisioner DaemonSet schedules onto nodes
# carrying this LABEL — it drives nodeAffinity, so it must be a label, not an
# annotation:
kubectl label node --all brewlet.sh/provision=true
You can also run the operator locally against your current kubeconfig (useful for debugging), passing the same inventory the chart would set:
make -C kubernetes operator-build
./kubernetes/bin/operator \
--namespace=brewlet \
--provisioner-image=<registry>/node-provisioner:<tag> \
--jdks=temurin-21,microsoft-25 \
--launchers=jaz
The RuntimeClass and provisioner DaemonSet the operator generates mirror
deploy/runtimeclass.yaml and
deploy/node-provisioner.yaml. All operator
and admission flags are in Configuration.
The operator itself does not need to be privileged — it only talks to the API server. The privileged, host-mutating work is done by the DaemonSet it manages.
Verify the installation¶
# Install the CLI version that matches the chart, then run the readiness check:
export BREWLET_VERSION="0.1.0"
curl -fsSL https://brewlet.sh/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
brewlet doctor --namespace default
# 1. Components are running:
kubectl get pods -n brewlet
# 2. Nodes are being provisioned → ready:
kubectl get nodes -L brewlet.sh/runtime
# NAME STATUS RUNTIME
# node-1 Ready ready ← provisioned
# 3. Inspect what a node advertises:
kubectl get node node-1 -o jsonpath='{.metadata.annotations.brewlet\.sh/jdks}{"\n"}'
# temurin-21,microsoft-25
kubectl get node node-1 -o jsonpath='{.metadata.annotations.brewlet\.sh/launchers}{"\n"}'
# java,jaz
# 4. The RuntimeClass exists:
kubectl get runtimeclass brewlet
# 5. The operator's view of each node:
kubectl get node node-1 -o jsonpath='{.metadata.annotations.brewlet\.sh/provision-state}{"\n"}'
# Ready
Watch provisioning events if a node isn't going ready:
kubectl get events --field-selector reason=NodeReady
kubectl get events --field-selector reason=ProvisionFailed
See Troubleshooting if a node stays Provisioning/Failed.
Smoke test with a workload¶
kubectl apply -f kubernetes/deploy/raw-deployment.yaml
kubectl get pods -l app=hello -w
kubectl logs -l app=hello
For the full deploy story (raw Deployment, JavaApplication CRD, requesting a
specific JDK/launcher), see Deploying workloads.
Uninstall¶
Uninstalling deletes the control-plane components and the chart's
NodeProfileobjects. Each profile carries anode.brewlet.sh/cleanupfinalizer, so the operator holds the object while a short-livedbrewlet-cleanup-<profile>DaemonSet (BREWLET_MODE=cleanup) restores theconfig.tomlbackup, removes the shim + JDK roots, and drops the runtime + capability labels on every assigned node — reversing host state automatically before the object is garbage-collected (§5.6). Watch it withkubectl get daemonset -n brewlet -w. If a cluster was provisioned the older way (a barebrewlet.sh/provision=truenode label with no profile), drain and clean those nodes (or replace them) to fully reverse provisioning.
Next steps¶
- Configuration — tune every knob.
- JDK management — add/patch JDK roots (copy-from-image), go multi-arch.
- Launchers — install and use
jaz.