infra.rbs or BUILD.rbs files anywhere in your workspace. A bare rbs infra plan discovers all of them — colocating each service’s infrastructure with its code across many packages is a fully supported layout, with no aggregator file required. A targeted invocation (rbs infra plan //backend:*) scopes to that package.
Configure a provider
Register the provider you target and give it its settings:source = "namespace/name" is how rbs knows what to download.
Declare cloud-agnostic resources
Load the abstract rules from the prelude and declare what you need. The same declaration targets any supported cloud through theproviders list:
instance_type = "medium" maps to the right machine size on each cloud, and cloud-native values ("t3.micro") pass through untouched when you need a specific SKU. Attribute values are validated at declaration time, and referencing one resource from another (network = vpc) both wires the dependency and resolves the real identifiers at apply time.
Environments and variables
Declare environments with their variables, read them withinfra.var, and select one with -e:
dev and prod are fully independent stacks of the same declarations.
Scale by loop, not by copy
Definitions are ordinary.rbs code, so a monorepo provisions per-service infrastructure with a list comprehension:
rbs infra plan shows exactly one create, and everything else plans as unchanged.
Provider-specific resources
When an abstraction doesn’t cover what you need, declare the concrete resource type directly withinfra.resource. Every resource type of the built-in providers is available, typed and validated against the provider’s real schema:
:name, or to one of its attributes as :name.attribute — the value is filled in from real state during apply, in dependency order:
depends_on = [":other"] for ordering that no attribute expresses.
Keep provider choice a variable even at this level with the cross-cloud equivalence table — infra.resolve maps a kind to the concrete type per provider:
Wire services to infrastructure
Theservice rule binds a binary to the resources it uses. You declare what the app needs, never how to reach it:
rbs run //backend:api-svc builds the binary, resolves the environment from applied state, and launches the process with it injected. Each resource type declares conventional exports, so uses = ["maindb"] is usually all the wiring a service needs:
Other types export similarly (
KMS_KEY_ID, TOPIC_ID, LOG_GROUP, …). Anything the conventions don’t cover — or an attribute a provider doesn’t populate — is wired explicitly with needs.
Resolution happens at run time, not build time: infra changes take effect on the next run without a rebuild, and secrets never land in launcher scripts on disk. Outside rbs run, the same resolution is available on the command line:
oci_push rule accepts the same uses — uses = ["api-registry"] resolves the push repository from the registry rbs provisioned, never a hardcoded URL.
Enforce policies
Policies run over declared resources on every plan;severity = "error" blocks apply. A baseline compliance pack ships in the binary — load it to enforce encryption at rest, no public data stores, and no open admin ingress across all providers at once:
infra.define_policy the same way — one control covers every cloud because policies see the abstract declarations.
Group with modules
infra.module namespaces a group of resources so a stack can be instantiated more than once without name collisions:
team-a.vpc, team-a.db in plans and state; modules nest, and the definition’s return value becomes the module’s outputs.
Grow the catalog
When you need resource types beyond what ships in the binary — a newer provider version, or a provider rbs doesn’t embed — generate typed definitions into your workspace:rules/infra/embedded/<provider>/ by default, where they auto-load exactly like the built-in catalogs: infra.resource(type = "cloudflare_...") just works, version-pinned. The files are yours — edit freely or regenerate.
To build a new cloud-agnostic abstraction of your own, scaffold it across providers and refine:
infra/ — the mechanical 80%. The judgement pass is yours: rename attributes to a shared vocabulary, add companion resources, wire outputs, then validate with rbs infra verify.