The Toolchain (.bin/b.yaml)
Every lok8s project carries a pinned toolchain in .bin/. The file that declares it is .bin/b.yaml, managed by b, a binary manager and env-file syncer (binary.help). lok8s itself is distributed as a b environment: b installs the lo CLI, the .lok8s/ framework tree, and every tool the framework calls.
This page documents what b does in a lok8s project and the b.yaml fields lok8s actually uses. For the full b feature set, see the upstream docs.
Documented against b v4.18.x
b ignores keys it does not know, so a field name from a different version fails silently: the binary still installs, just with default behavior. If something here does not match what you see, check binary.help for your version.
How b fits the lo workflow
b owns two things in a lok8s project:
- Binaries: the
lorelease binary, kubectl, kustomize, sops, tilt, kind, and the rest land in.bin/, pinned per project. Nothing touches your system. - Framework files:
.lok8s/**(addons, driver templates, provider plugins, the frozen argsh CLI), the Tilt extension, the kustomize plugins, and.bin/b.yamlitself sync from the upstream lok8s repo.
The commands you run:
# A new project: the files (lo init project), then the toolchain: lo writes
# .bin/b.yaml, installs b into .bin/ and runs b install (new or existing project)
lo init project
lo toolchain install
lo toolchain doctor # verifies the pins landed
# Join an existing lok8s project: install the exact pinned toolchain
.bin/b install # (or `b install` with b on PATH)
# Pull binary upgrades for the unpinned entries (the pins stay)
.bin/b update
# CI: exit non-zero when anything is out of date
b version --check
# Verify installed artifacts against b.lock checksums
b verify
# The profile path — the framework tree + the frozen bash reference too
b env add github.com/kernpilot/lok8s#local && b installb install reads .bin/b.yaml and writes b.lock (versions + SHA256), so teammates and CI get the identical toolchain. Getting Started walks through the same commands, after installing the lo release binary.
lo toolchain install: the consumer file
A consumer project does not need the whole profile: lo toolchain install writes a .bin/b.yaml generated from the pins the running lo was built with, then bootstraps b and runs b install (lo init project writes the project files only and prints this command as the next step):
# my-project — the lok8s toolchain, managed by b (github.com/fentas/b · binary.help).
# lo-toolchain: managed pins — written by `lo toolchain install`
# Generated by lo v0.5.0 (core); `lo toolchain install` never overwrites this file, it
# prints a diff. Run `.bin/b install` to (re)install; `lo doctor` verifies what landed.
# … (the header explains the pins, the groups and how to add the bash-side tools)
binaries:
# ── core: what `lo` execs on every path
kubectl:
groups: [core]
kustomize:
version: v5.8.1
groups: [core]
github.com/mgoltzsche/khelm:
version: v2.8.0
file: ../.kustomize/khelm.mgoltzsche.github.com/v2/chartrenderer/ChartRenderer
groups: [core]
github.com/kernpilot/lok8s:
version: v0.5.0 # this lo's own version
asset: kustomize-secret-*
file: ../.kustomize/secrets.lok8s.dev/v1/secret/Secret
groups: [core]
# ── local: the kind + Tilt dev loop (lo up, lo tilt, lo trust)
kind:
groups: [local]
tilt:
groups: [local]
mkcert:
groups: [local]
# ── cloud: the provisioning drivers (KubeOne on Hetzner) — opt-in
# github.com/kubermatic/kubeone:
# groups: [cloud]
# hcloud:
# groups: [cloud]- The pins are drift-tested.
kustomize v5.8.1is the CLI release built from the kustomize API thelo-fullbuild links (v0.21.1);khelm v2.8.0is the librarylo-fulllinks and theChartRendererbinarylocore execs; thesecrets.lok8s.devSecret plugin is thekustomize-secret-<os>-<arch>asset of thislo's own release. All three live ininternal/toolchain/pins.goandgo testfails whengo.modor the template disagrees. The exec render (core) and the in-process render (full) stay byte-identical because the numbers cannot drift apart. - Groups are a lok8s convention (
bignores the key):coreis always on,localby default,cloud(kubeone + hcloud) on--groups core,local,cloud. Entries outside the selection are emitted commented out: uncomment and.bin/b install. - Never overwritten. An existing
.bin/b.yamlstays byte-for-byte;lo toolchain installprints a unified diff against the template and the instructions, andlo toolchain doctorreports which installed tool differs from a pin. - Not in the consumer file:
argsh,yq,jq,envsubst,sops,ssh-to-age. The Go binary links or reimplements them (sops and ssh-to-age as libraries; the split-modeyq/sopssubprocesses are contributor territory today, and the tree a project routes commands to). To run the frozen bash implementation in a consumer project, add (as the file's header says)github.com/arg-sh/argsh: {asset: argsh, onPost: "${B_BIN} builtin ${B_EVENT}"},yq: {},jq: {},renvsubst: {alias: envsubst},sops: {},ssh-to-age: {}and run.bin/b install. bitself is installed into.bin/bfrom its pinned release tarball (b-<os>-<arch>.tar.gz, the asset b's own installer resolves), verified against the SHA-256 recorded from that release'schecksums.txtbefore extraction. Nocurl | sh.--dry-runshows the URL and the sum.GITHUB_TOKENis passed through when set (public sources need none). b publishes no darwin build: on macOS install b by hand (binary.help) and re-run.
What b manages: consumers vs contributors
Two files, two audiences. The consumer file is what lo toolchain install writes (above); the contributor file is the lok8s repo's own .bin/b.yaml, which the profiles publish and which still carries the bash side.
| Binary (group) | Consumer (lo toolchain install) | Contributor / profiles | Who needs it |
|---|---|---|---|
kubectl (core) | yes | yes | lo execs it for apply/wait |
kustomize v5.8.1 (core) | yes, pinned | yes | lo core: every render. lo-full: only LO_RENDER=exec; the bash side always |
khelm ChartRenderer v2.8.0 (core) | yes, pinned, under .kustomize/ | yes | the addons' Helm charts (core execs it; lo-full links the same release) |
secrets.lok8s.dev Secret plugin at lo's version (core) | yes, pinned, under .kustomize/ | built from source (lo kustomize build) | every render on core and the bash side; lo-full serves it itself |
kind, tilt, mkcert (local) | yes (default on) | yes | the local dev loop: lo up (kind), Tilt, lo trust (mkcert) |
kubeone, hcloud (cloud) | opt-in (--groups …,cloud) | kubeone/capi profiles (+ clusterctl) | the provisioning drivers exec them |
docker | no (install Docker yourself) | oci://docker | the Lo driver and Tilt |
argsh, jq, yq, envsubst, sops, ssh-to-age | no: the binary links or reimplements them (add them by hand when lok8s.yaml routes commands to bash; the file's header shows how) | yes | the frozen .lok8s/lo, the provider plugins, lo build split mode (yq/sops subprocesses), the bats suites |
bats | no | yes | contributors running tests/ |
lo itself | no — you installed it (lo-install.sh) | the core profile declares the release asset | everyone |
For a consumer the file above is the whole toolchain; lo doctor tells you when a pin is missing or off. For a contributor everything in the right column is needed, because the parity harnesses and the bats suites really run the argsh side; b install in a clone of the repo gets it.
Intended end state (planned; each step is sequenced separately): argsh, jq and bats leave the contributor core group once the provider plugins have Go twins and lo chat drives the Go lo mcp start (the shipped .mcp.json already does); sops and ssh-to-age follow once lo build split mode encrypts through the library; yq stays until the renderer-drift rule in The Go lo binary is satisfied. kustomize, khelm, kind, tilt, mkcert, kubectl and the driver CLIs stay: they are the tools lok8s orchestrates, not implementation details, and on lo core the first two are the renderer.
Install directory
b picks the install directory from the first of these that is set: PATH_BIN, then PATH_BASE, then <git-root>/.bin, then <cwd>/.bin. Note that b uses PATH_BIN and PATH_BASE verbatim: it does not append .bin to them, it only does that for the git-root and working- directory fallbacks. The .envrc that ships with every profile exports these for direnv users.
Authentication
Public sources need no token. Set GITHUB_TOKEN only for private repos or to raise GitHub API rate limits.
The binaries section
binaries is a map. Each key is either a pre-packaged name (kubectl, jq, sops, …: b search <name> lists them) or a provider ref (github.com/arg-sh/argsh, oci://docker, go://…, git://…). An empty value {} means "latest, defaults".
Fields lok8s uses, from the real .bin/b.yaml:
binaries:
oci://docker: {} # docker CLI from an OCI image, daemonless
renvsubst:
alias: envsubst # install under a different name
groups: [core] # profile tag (see below)
github.com/arg-sh/argsh:
asset: argsh # pick this release asset by glob
groups: [core]
onPost: "${B_BIN} builtin ${B_EVENT}" # hook after install/update
github.com/mgoltzsche/khelm:
file: ../.kustomize/khelm.mgoltzsche.github.com/v2/chartrenderer/ChartRenderer
groups: [kustomize] # custom install path (relative to b.yaml)| Field | Purpose |
|---|---|
version | Pin a version (tag). Without it, b installs the latest and b update upgrades. |
alias | Install the binary under a different name on PATH. |
asset | Glob that selects one release asset when a release ships several. |
file | Custom install path, relative to the b.yaml location. lok8s uses this to place kustomize exec plugins under .kustomize/. |
onPost | Shell hook that runs after a successful install or update, only when the binary on disk changed. Gets B_EVENT (install|update), B_NAME, B_VERSION, B_FILE. |
groups | Not a b field. A lok8s convention: b preserves unknown keys, and the profile select expressions below filter on this tag. |
The profiles section
profiles is b's env-sync feature: an upstream repo publishes named file sets, and consumers subscribe with b env add github.com/kernpilot/lok8s#<profile>. lok8s publishes five:
| Profile | Includes | Adds |
|---|---|---|
core | (none) | .lok8s/**, .envrc, .gitignore, .mcp.json, skills, and the core-tagged binaries |
kustomize | (none) | .kustomize/** plugins and their binaries |
local | core + kustomize | Tiltfile, services.yaml, kind/Tilt/mkcert/bats |
capi | local | clusterctl, hcloud |
kubeone | local | kubeone, hcloud |
Each profile entry has a description, optional includes (compose from other profiles), and a files map of glob patterns to sync. One pattern deserves a note. The profile syncs a filtered b.yaml:
profiles:
core:
files:
.lok8s/**:
.bin/b.yaml:
select:
- "{binaries: from_items(items(binaries)[?[1].groups && contains([1].groups, 'core')])}"select extracts keys from a YAML file instead of syncing it whole; a JMESPath expression here keeps only the binaries tagged with the profile's group. The effect: a core consumer's .bin/b.yaml lists only the core binaries. Each profile ships the tools it needs and nothing else.
You rarely touch profiles as a consumer. b env add copies the resolved profile into your local b.yaml, and b update keeps it in sync.
Adding your own tools
Your project's .bin/b.yaml is yours after sync. To add a tool:
b install --add github.com/derailed/k9s # install + record in b.yaml
b install --fix jq@1.7 # install + pin the versionOr edit .bin/b.yaml directly and run b install. Commit b.yaml and b.lock so the whole team gets the same tool.
See also
- Getting Started: profiles and the bootstrap path
bon GitHub · binary.help: the full manual (providers, env-sync strategies, Docker usage)