Resources
A resource block declares a piece of infra that should exist.
resource "<kind>" "<name>" {
<attr> = <value>
...
}
resource_block ::= "resource" string string "{" body "}"
body ::= ( attr | nested_block )*
The two labels are positional:
<kind>— the resource kind, e.g.docker_container. The prefix before the first_selects the provider.<name>— a stable identifier unique within the kind. The pair<kind>.<name>is the address used everywhere else (state file, plan output,stratum state show).
The address (kind, name) must be unique within the document; duplicates error.
Kind-to-provider routing
stratum splits the kind on the first _ and uses the prefix as the provider name. The kind must contain at least one underscore and must not start with one.
| kind | provider |
|---|---|
system_package | system |
ssh_exec | ssh |
docker_container | docker |
Anything without an underscore-prefix (foo, _bar) is rejected with resource kind ... must be prefixed with provider name.
Example
resource "docker_container" "hello" {
host = host.primary.addr
name = "hello"
image = "nginxdemos/hello:latest"
restart = "unless-stopped"
networks = ["stratum-edge"]
labels = {
"traefik.enable" = "true"
"traefik.http.routers.hello.rule" = "Host(`hello.example.com`)"
}
}
Nested blocks
A resource body may contain nested blocks. They are folded into the parent map. If the nested block has labels, the key is <kind>_<label1>_<label2>...; with no labels it is just <kind>.
resource "ssh_exec" "demo" {
host = host.prod.addr
command = "true"
meta {
owner = "ops"
}
}
Stored attrs:
{
"host": "root@192.0.2.10",
"command": "true",
"meta": { "owner": "ops" }
}
Provider implementations today expect attributes, not nested blocks — see each provider page for the shape it reads.
What providers see
After parsing and ref resolution, each resource body is a serde_json::Value::Object. Providers receive that object and pick the fields they care about. Unknown fields are ignored — there is no validation step between the parser and the provider.