.rbs/bin/ — and the apply is a deliberate
run-time side effect:
rbs deploy subcommand; deploying is running a deploy target. Two
rules cover the surface:
k8s_workload— a typed wrapper that generates correct YAML for the common shapes: Deployment + Service, StatefulSet + volume claims + headless Service, a Secret populated from your environment, and an Ingress.k8s_deploy— the underlying rule: hand-written manifest templates with${VAR}substitution. Anything beyond the wrapper’s shapes belongs here.
Declaring a workload
k8s_workload generates the YAML and declares a k8s_deploy target named
<name>_deploy carrying it:
k8s_workload parameters
The wrapper is deliberately small: kinds beyond deployment/statefulset, extra pod
settings, network policies, or multi-path ingresses belong in hand-written YAML
passed to
k8s_deploy — not in more wrapper options.
Hand-written manifests with k8s_deploy
When you need full control, write the YAML yourself and declare it:
At least one of
manifests or manifest_content must be non-empty.
The substitution model
Placeholders are${VAR}, resolved in two phases:
- Build time —
substitutions(static strings) render into the output manifests under.rbs/bin/. Anything unresolved stays as-is. - Deploy time —
images(push targets or literal refs),uses(exported values of applied infra resources), andenv(names read from the deploying shell). Substituted manifests are piped to kubectl on stdin, never written to disk — which is exactly why secrets flow throughenv.
k8s_workload prefixes its
generated files (00-secret, 05-pvc-*, 10-deployment, 20-service,
30-ingress) so Secrets land before the pods that mount them — name
hand-written files the same way when ordering matters.
Image references that point at an
oci_push target resolve through the same
repository resolution the push itself uses (explicit repository, or infra
state via uses). Push and deploy therefore cannot disagree about which
registry an image lives in.How the apply actually runs
rbs run on a deploy target builds it (rendering manifests), resolves the
deploy-time substitutions, then drives kubectl:
- kubectl is hermetically pinned. rbs downloads its pinned kubectl release
on first deploy and verifies it against the SHA-256 Kubernetes publishes.
PATHis never consulted — whatever kubectl a machine happens to have is exactly the non-hermeticity the pin removes. No kubectl installation is a prerequisite. KUBECONFIGpasses through untouched; the cluster you talk to is the one your kubeconfig selects.RBS_KUBE_CONTEXToverrides the target’scontextattribute per invocation.RBS_K8S_ACTION=deleteturns the run intokubectl delete(with--ignore-not-found), tearing down everything the target applied. Onlyapplyanddeleteare accepted.- The target
namespaceis created idempotently before the apply. RBS_KUBECTLis an explicit operator override (air-gapped mirrors, test shims). The executor logs loudly whenever it is in effect.
Pinning a kubectl version
rbs embeds a default kubectl version, so nothing is required for deploys to work. To pin a different release, register the toolchain inWORKSPACE.rbs:
dl.k8s.io works; the download is verified against its
published checksum.
A full deploy sequence
A realistic order for a stack of services (images first, then data layer, then services, then edge):RBS_K8S_ACTION=delete rbs run //deploy:<target>.
PersistentVolumeClaims survive deletion — remove them explicitly if you mean to
lose the data.