Skip to main content
rbs builds OCI-compliant container images without Docker. The oci_image rule packages a built binary (plus any extra files) into a Docker-compatible tar you can docker load or podman load, and oci_push uploads it to a registry. Building stays pure — the push is a deliberate side effect that only happens at rbs run. The rules live in one module:

A service image, end to end

The typical shape: cross-compile the binary for the cluster’s platform, package it on a minimal base, and declare the push target next to it.
The built tar lands in your workspace at .rbs/bin/<platform>/<package>/<name>/<name>.tar and loads straight into a local daemon:

oci_image

Creates an OCI image from a binary target and optional extra files. The packaged binary’s entire output directory (launcher, runfiles/, toolchain links) is copied into the image at /app/<binary>/, with build caches excluded, so the binary runs the same way in the container as it does under rbs run.

The image platform is checked against your binary

oci_image reads the packaged binary’s own executable header to decide (and verify) the image’s os/architecture stamp. Packaging a macOS binary into an image stamped linux is a build error, not a surprise at pod-scheduling time — cross-compile the binary for the image platform instead (for Go, that is go_binary’s goos/goarch attributes).

Working with scratch and other minimal bases

Two things bite everyone once, so they are worth knowing up front:
  • scratch has no shell. The default entrypoint is the binary’s launcher script, which needs one. On scratch, point entrypoint at the real executable inside the packaged directory: /app/<binary>/runfiles/_main/<binary>.bin.
  • Your image config replaces the base image’s config wholesale — the base contributes layers only. If the base relied on its config (a PATH, an entrypoint, default env), re-declare what you need in env/entrypoint. A base like alpine/git won’t find git at runtime unless you set PATH yourself.
scratch also carries no CA certificates: a service that makes outbound TLS connections needs a base that ships them (or a mounted certificate bundle).

Base images are cached user-globally

Base images referenced by base are pulled from the registry once and cached in the user-global cache (~/.cache/rbs/oci-images, or under $RBS_CACHE_DIR when set) — shared by every workspace on the machine. Layer content is checksum-verified both when fetched and when loaded from cache, and the cache is swept by the same age bounds as everything else under rbs cache gc.
Referencing a locally built tar as a base (base = "@some_target") is not supported yet — base must be "scratch" or a registry reference.

oci_tarball

Packages a plain directory (rather than a binary target) into a Docker-compatible image tar. Useful for static assets or prepared filesystem trees.

Pushing to a registry with oci_push

oci_push declares where an image goes; the push itself happens only when you rbs run the target. There is no rbs push subcommand — pushing is a run-time side effect, keeping rbs build pure and repeatable.
Either repository or uses must resolve to a repository — a push target with neither fails with a clear error.

Credentials

The push authenticates from environment variables in the pushing shell:
For ECR, use RBS_OCI_USERNAME=AWS with a password from aws ecr get-login-password. Local registries on localhost need no credentials.

Resolving the repository from infra

When your registry is provisioned through rbs infrastructure, skip the hardcoded repository and let the push target read it from applied state:
After rbs infra apply creates (or confirms) the registry, the push target resolves its repository URL from the resource’s exported REGISTRY_URL — a new service’s push target needs no editing when registries move. Kubernetes deploy targets resolve image references through the same path, so what you push and what you deploy can never disagree — see Deploying to Kubernetes.