Skip to content

Configuration

Every knob Brewlet exposes, in one place: Helm chart values, node-provisioner environment variables, operator/admission flags, the RuntimeClass, and how the different layers relate. For how to install, see Installation; for JDKs and launchers specifically, see JDK management and Launchers. For how the resulting node labels drive scheduling and autoscaling, see Capability labels and autoscaling. For the operational workflow behind the metrics values, see Runtime metrics and Grafana dashboards. For runnable-image cache reclamation and consumer-safety prerequisites, see Runnable stage cleanup.


Configuration layers (how they connect)

There is one source of truth for the JDK/launcher inventory: each NodeProfile contains complete, immutable source declarations.

Helm values (provisioner.jdks / .launchers + source-mirror allowlist)
        │  render NodeProfiles and operator/admission flags
        ▼
Admission + reconciliation validate each digest, path, name, and mirror
        │  render indexed source variables into a DaemonSet
        ▼
Provisioner env (JDK_SOURCE_* / LAUNCHER_SOURCE_* / mirror policy)
        │  derives JDKS and LAUNCHERS, then copies approved image content
        ▼
Node state: /opt/brewlet/jdks/<dist>-<feature>/ + capability labels/annotations

If you use Helm, set values. If you run the operator directly, set flags. If you hand-wire the DaemonSet, set env vars. Don't mix — the operator overwrites the DaemonSet it manages.

Note on examples. The Helm chart is installable with editable, digest-pinned Temurin 21, Microsoft JDK 25, and jaz entries. They are ordinary values, not a Brewlet-maintained catalog. The manager binary has no runtime inventory defaults; outside Helm you must create a NodeProfile.


Helm chart values

From charts/brewlet/values.yaml. Override with --set key=value or a values file.

Key Default Meaning
namespace brewlet Component namespace, created by the chart and retained on uninstall to protect unrelated objects. May differ from the Helm release namespace.
images.registry ghcr.io/microsoft Registry prefix used to generate component image references.
images.tag chart appVersion Shared component tag. A versioned OCI chart therefore selects matching images automatically.
images.operator generated Explicit operator image override; supports tags or digests.
images.provisioner generated Explicit provisioner image override; supports tags or digests.
images.admission generated Explicit admission webhook image override; supports tags or digests.
images.digests.operator recorded at release Immutable sha256:<64 hex> digest for the operator image. Set by the release workflow in published charts; renders <registry>/brewlet-operator@<digest> and takes precedence over images.tag.
images.digests.provisioner recorded at release Immutable digest for the node-provisioner image.
images.digests.admission recorded at release Immutable digest for the admission webhook image.
images.pullPolicy IfNotPresent Image pull policy for all components.
security.allowedSourceMirrorHosts [] Exact registry destination hosts, including explicit ports, that NodeProfiles may use for JDK/launcher mirrors. Empty disables mirrors. The chart passes the same list to manager and admission.
provisioner.pools [] (required) Node pool names the chart-managed default NodeProfile may provision. Use names exactly as your provider reports them (AKS pool names are lowercase alphanumeric, with no dashes). The privileged provisioner is never cluster-wide by default: rendering fails when defaultProfile.enabled is true and this is empty.
provisioner.poolKey "" Node label carrying the pool name. Empty auto-detects the well-known provider keys (AKS, EKS, GKE); set it explicitly on bare metal or kubeadm.
provisioner.includeControlPlane false Allow the default profile onto control-plane nodes. Off by default, and only needed on single-node clusters such as kind or Docker Desktop, which label their node as the control plane without tainting it.
provisioner.tolerations [] Tolerations for the default profile's provisioner pods. Standard Kubernetes toleration entries; each must name a key. Nothing is tolerated implicitly.
provisioner.jdks [] (required) JDK entries with distribution, feature, digest-pinned source.image, and absolute source.javaHome (§JDK management). Brewlet ships no built-in runtime catalog, so rendering fails when defaultProfile.enabled is true and this is empty.
provisioner.launchers [] Optional launcher entries with name, digest-pinned source.image, and absolute source.path (§Launchers). Empty = vanilla java only.
provisioner.appCDS.regenerationEnabled false Authorize node-side AppCDS regeneration for the chart-managed default NodeProfile.
provisioner.rollout.maxUnavailable null Bounds the default profile's provisioner DaemonSet rolling update. null keeps the DaemonSet default.
provisioner.rollout.validate true Gate node readiness on post-install JDK smoke tests and staged-launcher executable checks. Arbitrary launchers are not executed as a probe. Renders the provisioner BREWLET_VALIDATE env.
provisioner.rollout.containerdRestart validated Select containerd activation: transactional config validation, service restart, live health checks, and rollback (validated), or no containerd mutation/signal (none). Invalid values are rejected. Renders BREWLET_CONTAINERD_RESTART (§5.5).
provisioner.registry.mirrors {} <upstream-host>: <mirror-host[/path]> map applied to digest-pinned source pulls. Every destination host must appear in security.allowedSourceMirrorHosts.
defaultProfile.enabled true Render the chart-managed default NodeProfile from provisioner.*. Requires provisioner.pools. Disable to manage the default profile yourself, e.g. via GitOps (§5.6).
profiles [] Additional NodeProfile CRs. Each requires a unique name, a nonempty list of named pools, and explicit JDK sources; the chart never renders an accidental catch-all. Includes AppCDS, rollout, and registry policy (§5.6).
operator.replicas 1 Operator replica count.
operator.leaderElect true Enable leader election for HA.
operator.nodeSelector {} Node labels the operator pod must match (for example, to keep it on a system pool).
operator.resources requests 50m/64Mi, limits 200m/128Mi Operator pod resources.
stageGC.enabled true Run periodic orphaned runnable-image stage cleanup in every managed provisioner, independently of metrics. Fresh nodes activate automatically; existing installations require a matching installation safety record.
stageGC.interval 5m Delay after each attempt, plus up to 10% jitter. Sweeps do not overlap.
stageGC.minAge 24h Minimum stage directory age, not time since last use or since becoming unreferenced.
stageGC.allowNestedPIDNamespace false Test-only. Let the reaper run on nodes that are themselves containers with a private PID namespace (kind). Never enable on real nodes.
uninstall.timeoutSeconds 240 Pre-delete cleanup coordinator timeout, 1-86400 whole seconds. Configure before uninstalling; Helm's --timeout must exceed this plus 30 seconds. Failure retains the control plane. See Uninstall.
uninstall.imagePullSecrets [] Namespaced registry Secret references for the cleanup Job, which uses the operator image and its own service account.
uninstall.nodeSelector {} Extra node labels for the cleanup Job pod; kubernetes.io/os: linux is always set.
metrics.enabled false Opt in to Brewlet runtime and control-plane Prometheus endpoints. Enables the operator and admission listeners plus the node exporter sidecar and Services.
metrics.nodePort 9090 Port exposed by each node-local metrics exporter and the brewlet-node-metrics headless Service.
metrics.serviceMonitor.enabled false Create a Prometheus Operator ServiceMonitor. Requires the monitoring.coreos.com/v1 CRDs.
metrics.serviceMonitor.interval 30s Scrape interval rendered into the optional ServiceMonitor.
metrics.serviceMonitor.additionalLabels {} Extra ServiceMonitor labels, commonly used to match a Prometheus serviceMonitorSelector.
metrics.grafanaDashboard.enabled false Create the brewlet-grafana-dashboard ConfigMap for dashboard sidecar discovery; does not install Grafana or a data source.
metrics.grafanaDashboard.labels grafana_dashboard: "1" Labels applied to the dashboard ConfigMap for sidecar discovery.
admission.enabled true Deploy the admission/scheduling webhook. Set false to skip it; the shim still enforces runtime image identity and JDK compatibility.
admission.replicas 1 Webhook replica count.
admission.failurePolicy Ignore Webhook failure policy. Ignore keeps availability high, but runtime identity resolution still fails closed in the shim if containerd metadata cannot be resolved.
admission.nodeSelector {} Node labels the admission webhook pods must match.
admission.port 9443 Webhook server port.
admission.namespaceSelector excludes kube-system Namespaces the cluster-wide pod webhook is consulted for.
admission.platformNamespaceExclusions AKS Admissions Enforcer expressions Appended to the namespaceSelector of every chart webhook so Helm 4 (server-side apply) upgrades on AKS do not conflict with the admissionsenforcer field manager. See the chart README's "AKS Admissions Enforcer" section.
admission.disableAKSAdmissionsEnforcer false AKS only, not recommended: opt both webhook configurations out of the Admissions Enforcer with admissions.enforcer/disabled: "true".
admission.resources requests 50m/64Mi, limits 200m/128Mi Webhook pod resources.

Example production values (own runtime source, no optional launcher):

# values-production.yaml
images:
  operator: registry.example.com/brewlet/operator@sha256:<digest>
  provisioner: registry.example.com/brewlet/node-provisioner@sha256:<digest>
  admission: registry.example.com/brewlet/admission@sha256:<digest>
provisioner:
  # The privileged provisioner only ever touches the pools you name here.
  pools: ["javaworkers"]
  poolKey: agentpool
  jdks:
    - distribution: platform
      feature: 21
      source:
        image: registry.example.com/java/runtime@sha256:<digest>
        javaHome: /opt/java/runtime
  launchers: []
helm upgrade --install brewlet oci://ghcr.io/microsoft/charts/brewlet \
  -f values-production.yaml

This selects the latest released chart. To pin a release, add --version "$BREWLET_VERSION" with its concrete version, and ensure any component-image overrides match it. For an existing installation, follow Upgrading to update matching CRDs and retain your values.

JDKs and launchers are always obtained copy-from-image from explicit, tagless SHA-256 digest references. For air-gapped clusters, mirror the exact digest and approve the destination host explicitly.

Runnable-image stage cleanup

stageGC.* is operator-wide policy, not a NodeProfile.spec field. It applies to chart-rendered and externally managed profiles alike. Configure it through Helm values or operator flags, not by patching a reconciled DaemonSet. interval and minAge accept Go durations that resolve to positive whole seconds, at most 2147483647s; the operator rejects invalid values.

Kubelet and containerd still own image retention and content reclamation. Brewlet removes only old, unreferenced, unmounted published stages; it does not copy kubelet's disk thresholds or change kubelet configuration. Containerd references in any namespace can delay reclamation. Unmanaged layouts and abandoned pending trees are not automatically removed.

For existing nodes, follow the GC activation procedure. Unverified installations stay blocked; there is no acknowledgment override. Readiness does not imply that GC is active. Setting stageGC.enabled=false stops automatic sweeps, not separately scheduled or manually invoked cleanup commands.

AppCDS regeneration

AppCDS regeneration is default-deny. Enable it for the default profile with --set provisioner.appCDS.regenerationEnabled=true, or on a named profile with:

profiles:
  - name: appcds-builders
    pools: ["cdsbuilders"]
    jdks:
      - distribution: temurin
        feature: 21
        source:
          image: docker.io/library/eclipse-temurin@sha256:85f00967bcc624fc19fa9c2cf124ea426a5363898e267141726f31f358c2e14b
          javaHome: /opt/java/openjdk
    appCDS:
      regenerationEnabled: true

Where the provisioner may run

The provisioner is privileged and mutates the host, so its placement is a security boundary rather than a scheduling preference. Three rules decide it, and all three are closed by default.

A profile provisions only the pools it names. The chart will not render its default NodeProfile until provisioner.pools is set; an install that leaves it empty fails with an actionable message instead of quietly claiming the cluster. Set defaultProfile.enabled=false if you would rather author profiles yourself.

And only the JDKs it names. provisioner.jdks is required for the same reason a pool is: Brewlet has no built-in runtime catalog, and the platform team owns every digest it runs. The chart ships no default source, because a chart-pinned digest could never receive a CVE patch.

Control-plane nodes are excluded. Every profile's DaemonSet requires node-role.kubernetes.io/control-plane and node-role.kubernetes.io/master to be absent. This is a node-affinity rule, not a taint rule, because kind and Docker Desktop label their single node as the control plane without tainting it — a NoSchedule taint alone would not have kept the provisioner off. The same rule governs status.assignedNodes, so an excluded node is never counted and never holds the profile short of Ready.

Nothing is tolerated implicitly. The DaemonSet carries only the tolerations a profile declares, plus the node-condition tolerations Kubernetes injects into every DaemonSet. Each declared toleration must name a key, so a blanket "tolerate everything" entry cannot be written.

To provision a tainted pool, name the taint:

provisioner:
  pools: ["javaworkers"]
  poolKey: agentpool
  tolerations:
    - key: workload
      operator: Equal
      value: java
      effect: NoSchedule

On a single-node kind or Docker Desktop cluster, the only node is the control plane, so opt in explicitly:

helm upgrade --install brewlet oci://ghcr.io/microsoft/charts/brewlet \
  --set provisioner.pools="{control-plane}" \
  --set provisioner.poolKey=kubernetes.io/hostname \
  --set provisioner.includeControlPlane=true

Both settings exist per profile as well — pools, poolKey, includeControlPlane, and tolerations on each profiles[] entry, and spec.nodePool.includeControlPlane / spec.tolerations on a hand-authored NodeProfile.


Operator flags

The source for these flags lives in cmd/manager. When you install via Helm, the chart populates them for you.

Flag Default Meaning
--namespace brewlet Namespace the provisioner DaemonSet is managed in.
--provisioner-image ghcr.io/microsoft/brewlet-node-provisioner:0.1.0 Image the DaemonSet runs. Published Helm charts override this with the release image digest.
--allowed-source-mirror-hosts (empty) Comma-separated exact destination registry hosts approved for NodeProfile runtime-source rewrites. Configure the same value on manager and admission; empty disables mirrors.
--leader-elect false Enable leader election for HA.
--metrics-bind-address 0 Metrics endpoint; 0 disables it.
--health-probe-bind-address :8081 Health/readiness endpoint.
--node-metrics-enabled false Run the node-local exporter sidecar in managed provisioner pods.
--node-metrics-port 9090 Exporter port when node metrics are enabled.
--stage-gc-enabled true Enable periodic stage cleanup after successful provisioning and installation safety checks.
--stage-gc-interval 5m Delay between GC attempts, plus jitter; positive whole seconds in Go duration syntax.
--stage-gc-min-age 24h Minimum stage directory age; positive whole seconds in Go duration syntax.
--stage-gc-allow-nested-pid-namespace false Test-only. Let the reaper run on nodes with a private PID namespace (kind).

For a local operator built from the same release as the installed CLI, use that CLI's version to select the matching provisioner image. For custom source builds, use your own matching provisioner image instead.

BREWLET_VERSION="$(brewlet version)"
./bin/operator --namespace=brewlet \
  --provisioner-image="ghcr.io/microsoft/brewlet-node-provisioner:${BREWLET_VERSION}" \
  --allowed-source-mirror-hosts=registry.internal.example.com
kubectl apply -f nodeprofile.yaml

The Helm pre-delete Job selects a separate coordinator-only mode with --uninstall-release-name, --uninstall-release-namespace, and --uninstall-timeout. Both release identity flags are required. --namespace still identifies the worker namespace, which may differ from the release namespace. This mode does not start controllers or host-mutating workers; see Uninstall.


Node-provisioner environment variables

The provisioner environment contract lives in provisioner/README.md. The Kubernetes operator sets these variables on NodeProfile-managed DaemonSets; configure the profile and operator settings rather than editing worker environments.

Env var Default Meaning
JDK_SOURCE_COUNT required Positive number of indexed JDK source entries.
JDK_SOURCE_<n>_TOKEN required Safe <distribution>-<feature> inventory token.
JDK_SOURCE_<n>_IMAGE required Fully qualified, tagless repository@sha256:<64 lowercase hex> source image.
JDK_SOURCE_<n>_JAVA_HOME required Clean absolute Java-home path inside the image.
LAUNCHER_SOURCE_COUNT 0 Number of indexed optional launcher sources.
LAUNCHER_SOURCE_<n>_NAME required per entry Lowercase launcher name; java is reserved.
LAUNCHER_SOURCE_<n>_IMAGE required per entry Fully qualified, tagless SHA-256 digest source image.
LAUNCHER_SOURCE_<n>_PATH required per entry Clean absolute launcher path inside the image.
JDKS / LAUNCHERS derived Internal comma-separated inventories derived from the indexed entries. They are not accepted as independent inputs.
BREWLET_APP_CDS_REGENERATION_ENABLED false Internal operator-to-provisioner policy transport. When true, atomically creates the root-owned AppCDS authorization sentinel and publishes brewlet.sh/appcds-regeneration=true; when false or during cleanup/failure, removes both. Configure spec.appCDS.regenerationEnabled, not this variable directly.
BREWLET_STAGE_GC_ENABLED true Enable the periodic GC loop after provisioning, subject to installation safety checks. Rendered from operator-wide stageGC.enabled.
BREWLET_STAGE_GC_INTERVAL_SECONDS 300 Delay between GC attempts, plus up to 10% jitter; integer seconds in 1..2147483647.
BREWLET_STAGE_GC_MIN_AGE_SECONDS 86400 Minimum stage directory age; integer seconds in 1..2147483647.
BREWLET_STAGE_GC_ALLOW_NESTED_PID_NAMESPACE false Test-only. Pass --allow-nested-pid-namespace to the reaper on kind nodes; rendered from operator-wide policy.
BREWLET_RUNNABLE_STAGE /tmp/brewlet-runnable (provisioner) Host stage root used by automatic GC. A custom location must also be configured separately for the shim and exporter's read-only mount; this variable does not reconfigure those processes.
NODE_NAME (downward API) The node to label; injected from spec.nodeName.
BREWLET_PROFILE_NAME default Profile name paired with its UID in managed worker authority checks.
BREWLET_PROFILE_UID (empty) Operator-managed profile UID paired with node ownership and persisted writer authority. Required: provisioning and cleanup always verify node UID/owner labels and durable profile authority.
BREWLET_TARGET_UIDS (empty) Operator-generated immutable space-separated authorized Node UIDs for this worker. Both modes refuse writes outside this set; missing/empty sets authorize no host. Never supply this manually to adopt a host.
BREWLET_PROFILE_GENERATION 0 Operator-managed generation paired with BREWLET_PROFILE_UID.
BREWLET_PREFIX /opt/brewlet Host install prefix (bin/, jdks/, launchers/).
CONTAINERD_CONFIG /etc/containerd/config.toml Primary containerd configuration. Validated mode uses an imported drop-in when supported and otherwise patches this file with a backup.
CONTAINERD_DROPIN_DIR /etc/containerd/config.toml.d Drop-in directory used when the primary config imports *.toml from it.
CONTAINERD_DROPIN_FILE /etc/containerd/config.toml.d/99-brewlet.toml Brewlet-managed runtime drop-in.
CONTAINERD_ADDRESS /run/containerd/containerd.sock Host containerd socket for copy-from-image and stage GC reference inspection.
CONTAINERD_NAMESPACE k8s.io containerd namespace for image pulls; does not restrict GC, which checks all namespaces.
BREWLET_MODE provision provision installs the runtime; cleanup reverses recorded host state for a retiring target or deleted profile. Managed cleanup uses the frozen per-node containerd policy and runs in brewlet-cleanup-<profile> (§5.6).
BREWLET_CONTAINERD_RESTART validated validated validates the effective config with containerd config dump, restarts the host service only when needed, checks containerd and the live brewlet handler, and restores known-good config on activation failure. none neither mutates nor signals containerd during provisioning or cleanup. Rendered from spec.rollout.containerdRestart; other values fail before host operations.
BREWLET_VALIDATE true Run java -version for every JDK and verify every staged launcher is executable before publishing runtime or capability labels. Arbitrary launchers are not executed. false skips both sets of checks. Rendered from spec.rollout.validate.
MIRRORS (empty) Strict comma-separated <upstream-host>=<mirror-host[/path]> pairs rendered from spec.registry.mirrors. Schemes, whitespace, empty entries, duplicates, self-mappings, and unapproved destinations fail closed.
SOURCE_ALLOWED_MIRROR_HOSTS (empty) Exact destination host allowlist rendered from the operator's --allowed-source-mirror-hosts; empty disables MIRRORS.
SOURCE_POLICY_BIN /opt/brewlet-dist/brewlet-source-policy Baked validator for digest references, source paths, registry hosts, and mirror targets.

Admission webhook

The brewlet-admission webhook is mutating+validating. For every pod on CREATE with runtimeClassName: brewlet it:

  • overwrites brewlet.sh/artifact-container, brewlet.sh/artifact-ref, and brewlet.sh/artifact-digest (when digest-pinned) as compatibility hints from the selected Pod image; the shim resolves the JAR from containerd-owned metadata and the content store by digest, not from the hints;
  • matches any requested JDK/launcher/AppCDS-regeneration combination against the ready fleet plus valid NodeProfiles, denying with NoCompatibleJDK, NoCompatibleLauncher, or AppCDSRegenerationDisabled (a request only a profile satisfies is admitted with a warning, enabling scale-from-zero);
  • steers scheduling via nodeAffinity onto per-capability node labels.

Its NodeProfile endpoint also rejects mutable source references and mirrors outside --allowed-source-mirror-hosts. The NodeProfileReconciler repeats the same checks before creating a privileged DaemonSet, so webhook disablement or bypass does not weaken the source boundary.

Admission matches JDK and launcher capability keys with Operator: Exists. The Capability labels and autoscaling guide explains the end-to-end scheduling flow and autoscaler integration. The canonical capability-label contract defines the complete key grammar and compatibility guarantees.

Non-brewlet pods pass through untouched. With admission.failurePolicy: Ignore (default) a webhook outage never blocks workloads. For AppCDS regeneration this is only an availability choice: the shim independently requires the root-owned /opt/brewlet/policy/appcds-regeneration-enabled sentinel, so fail-open admission cannot grant cache-write authority. The shim also fails closed rather than guessing when it cannot resolve runtime image identity from containerd metadata.

Serving certificate. By default Helm generates a 90-day self-signed serving certificate and injects the CA as the caBundle. A helm upgrade regenerates the Secret and CA bundle together and rolls the webhook pods, but this simple mode does not renew certificates between Helm operations. Configure its lifetime with admission.selfSigned.validityDays. Treat it as the simple evaluation path: long-running installations must either enable cert-manager or schedule a Helm upgrade before the certificate expires.

For automatic renewal, install cert-manager with its CA injector and enable:

admission:
  certManager:
    enabled: true
    issuerRef:
      name: platform-ca
      kind: ClusterIssuer
      group: cert-manager.io
    duration: 2160h
    renewBefore: 720h

Set createSelfSignedIssuer: true instead of issuerRef.name when a chart-managed namespaced self-signed CA chain is appropriate. In either cert-manager mode, cert-manager creates and rotates the brewlet-admission-cert Secret and injects the CA into both webhook configurations. The admission process watches the mounted keypair and reloads renewed certificates without a pod restart.

admission.nodeProfileFailurePolicy defaults to Ignore so certificate bootstrap cannot block chart-created profiles. The operator still repeats every NodeProfile validation before privileged reconciliation. Set it to Fail once webhook availability is guaranteed if API transport failures must reject profile writes synchronously.

Network isolation. The chart can add ingress NetworkPolicies for the admission pod and all enabled metrics endpoints:

metrics:
  enabled: true
networkPolicy:
  enabled: true
  healthProbes:
    ingressFrom:
      - ipBlock:
          cidr: 10.1.0.0/16
  admission:
    apiServerCIDRs: ["10.0.0.0/24"]
  metrics:
    ingressFrom:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: monitoring
        podSelector:
          matchLabels:
            app.kubernetes.io/name: prometheus

The chart requires explicit kubelet health-probe peers, API-server sources, and metrics peers rather than guessing cluster-specific identities. Determine the source CIDRs used by your nodes and control plane, then verify health probes and webhook connectivity before rolling this setting into production. NetworkPolicies require a CNI that enforces the Kubernetes NetworkPolicy API.

When the API server reaches webhooks through in-cluster proxy pods, CIDRs alone may not match. For example, AKS dials pods through konnectivity-agent pods in kube-system, and Azure CNI powered by Cilium never applies ipBlock rules to in-cluster pods. Add those pods with networkPolicy.admission.ingressFrom:

networkPolicy:
  admission:
    ingressFrom:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
        podSelector:
          matchLabels:
            app: konnectivity-agent

Pod-side annotations the webhook reads (developer-facing) — see Deploying workloads:

Annotation Example Meaning
brewlet.sh/jdk 21 or temurin-21 Request a specific JDK feature (any distro) or exact <dist>-<feature>.
brewlet.sh/launcher jaz Request a launcher. Empty / java = vanilla OpenJDK launcher.
brewlet.sh/cds-regenerate true Request node-side AppCDS regeneration. Requires a ready node or valid NodeProfile whose spec.appCDS.regenerationEnabled is true.
brewlet.sh/artifact-container app Selects which regular container's image the webhook mirrors into Pod-wide compatibility hints. The webhook normalizes this value to the selected container name; other tasks ignore the shared hints and remain bound to their own CRI images.

RuntimeClass

The operator manages the brewlet RuntimeClass; this is what it generates (mirrors deploy/runtimeclass.yaml):

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: brewlet
handler: brewlet                 # matches the containerd runtime name
scheduling:
  nodeSelector:
    brewlet.sh/runtime: "ready"  # only land on provisioned nodes
overhead:
  podFixed:
    memory: "64Mi"               # JVM/runtime baseline overhead accounting
    cpu: "50m"

overhead.podFixed is how the scheduler and LimitRange/quotas account for the JVM/runtime baseline. Adjust it if your JVMs have a materially different fixed footprint.


Precedence & defaults you should know

  • JVM launch flags: artifact structured knobs carry app-intrinsic correctness flags; deployment-descriptor jvm.args carries heap/GC/agent tuning and comes after those knobs. The descriptor's jvm.launcher / brewlet.sh/launcher selects java or a node-installed launcher. Brewlet injects no -XX flags itself. See Resource requests, limits & JVM tuning.
  • JDK selection: the deployment descriptor is authoritative: spec.jvm.version (plus optional spec.jvm.distribution) on JavaApplication, or brewlet.sh/jdk on raw pods, drives validation, scheduling, and shim launch selection. A bare feature matches any distribution; the shim chooses the lexicographically first active, compatible inventory entry of that feature on each node. <distribution>-<feature> pins one inventory entry, not a patch version. Adding an earlier-sorting name can change future version-only launches, and different node inventories can produce different choices. See JDK selection and defaults.
  • JDK source selection: every spec.jdks[] entry carries exactly one digest-pinned source and Java-home path. Distribution names never resolve to Brewlet-owned images. Custom names such as temurin-stable and temurin-canary let two builds of the same feature coexist; define them in NodeProfile.spec.jdks[].distribution (or provisioner.jdks[].distribution for Helm's default profile). There is no separate patch/build selector in spec.jvm; the selected entry's source digest determines the build.
  • Mirror selection: a NodeProfile mapping is accepted only when its destination host exactly matches the external operator/admission allowlist.
  • cgroup v2 is mandatory on nodes; the provisioner refuses cgroup v1-only nodes.
  • Digest-pinned artifact refs are required for Kubernetes execution (repo@sha256:…). The shim resolves that exact target from the content store, verifies its platform-manifest config against CRI metadata, and rejects tag-only requests.

Next steps