Skip to main content
Infrastructure definitions live in 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:
For the built-in clouds this is mostly about configuration (region, project, subscription): using any resource type from the embedded catalogs automatically registers the provider at its pinned version. For any other Terraform-registry provider, 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 the providers list:
Attributes use a shared vocabulary — 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 with infra.var, and select one with -e:
Each environment has its own state, so 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:
Adding a service means adding one name to the list: the next 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 with infra.resource. Every resource type of the built-in providers is available, typed and validated against the provider’s real schema:
Unknown attributes are flagged at plan time, and the full provider-schema validation runs before anything is applied. References resolve at apply time. Refer to another resource as :name, or to one of its attributes as :name.attribute — the value is filled in from real state during apply, in dependency order:
References double as dependency edges; add 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

The service 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:
The oci_push rule accepts the same usesuses = ["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:
Write your own with 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:
Resources inside are named 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:
Generated files land under 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:
This fetches each provider’s schema, aligns matching attributes into a shared surface, and writes a draft abstraction plus one adapter per provider into 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.