References & scope
A reference is a dotted path of identifiers used in place of a literal value.
ref ::= ident ( "." ident )+
The first segment is the root. Three roots are supported:
| root | resolves to | shape |
|---|---|---|
host | a field on a declared host block. | host.<name>.<field> (3+ parts) |
secret | the resolved plaintext of a declared secret block. | secret.<name>.value (exactly 3 parts; value is the only allowed field) |
var | the resolved string of a declared var block. | var.<name> (exactly 2 parts; no field access) |
Anything else errors with unknown reference root.
Host references
Form: host.<name>.<field> (at least three segments).
host "prod" {
addr = "root@192.0.2.10"
port = 22
}
resource "ssh_exec" "uptime" {
host = host.prod.addr # -> "root@192.0.2.10"
command = "uptime"
}
Resolution walks the host's evaluated attrs as a JSON map. Each segment after the host name indexes one level deeper, so nested fields work too:
host "prod" {
ssh = {
user = "root"
addr = "1.2.3.4"
}
}
resource "ssh_exec" "x" {
host = host.prod.ssh.addr # -> "1.2.3.4"
command = "true"
}
Secret references
Form: secret.<name>.value. Exactly three segments — value is the only allowed field, since secrets are leaves.
secret "pg_password" {
from_env = "PG_PASSWORD"
}
resource "docker_container" "db" {
host = host.primary.addr
image = "postgres:16"
env = {
POSTGRES_PASSWORD = secret.pg_password.value
}
}
Secret refs are only allowed inside single-leaf string attrs. They are rejected at config-load time inside system_file.content, system_file.content_file, and system_dir.source_dir — see Secrets: the honesty guard for why.
Var references
Form: var.<name>. Exactly two segments — there is no field access, since a var resolves to a single string. Unlike host and secret, a var reference is almost always written inside a ${...} interpolation, because the resolved value is usually glued into a larger string (an image tag, a build command).
var "app_sha" {
from_git_sha = "../app"
default = "dev"
}
resource "docker_container" "app" {
host = host.primary.addr
image = "app:${var.app_sha}"
pull = false
}
A bare var.app_sha (outside ${...}) is also valid wherever a string value is expected, but the interpolation form is the common case. See Vars for sources and precedence.
Error cases
| condition | error |
|---|---|
Fewer than 3 segments (host.prod) | reference ... too short — expected at least 3 segments |
Unknown host name (host.ghost.addr) | unknown host ghost in reference ... |
Unknown field (host.prod.missing) | host prodhas no fieldmissing ... |
Unknown secret name (secret.ghost.value) | unknown secret ghost in reference ... |
Secret field other than value (secret.s.fingerprint) | reference to secret s has unsupported field ... |
Unknown var name (var.ghost) | unknown var ghost in reference ... |
Unknown root (provider.x.y) | unknown reference root provider`` |
Any ref inside a host body | references not allowed inside host blocks |
Any ref inside a secret body | references not allowed inside secret blocks |
Any ref inside a var body | references not allowed inside var blocks |
Secret ref inside system_file.content / content_file / system_dir.source_dir | secret reference not allowed in ... |
Why only these roots
The evaluator collects all leaf blocks first — hosts, then secrets, then vars — before it evaluates providers and resources (see Evaluation order). By the time a resource body is read, the host, secret, and var scopes are fully populated, so any reference into them resolves immediately.
There is no resource.foo.attr form: resources cannot reference each other. Cross-resource references would need a topological evaluation pass; that has not shipped. Ordering between resources is expressed with depends_on, not references.
References inside strings
A reference may also appear inside a string as ${<ref>} — the placeholder is replaced with the resolved scalar value at evaluation time. The same root rules apply (host.*, secret.*, and var.*), and the same honesty guard fires for forbidden attrs. See String interpolation for the grammar and edge cases.
env = {
DATABASE_URL = "postgresql://app:${secret.pg.value}@${host.primary.addr}:5432/app"
}
Bare identifiers as values
A single identifier with no dots is not a valid value. Either quote it as a string or extend it to a ref. The parser will report bare identifier ... not allowed as value (use a string or a reference like ...).
resource "ssh_exec" "x" {
host = prod # error: bare identifier
host = "prod" # ok — string
host = host.prod.addr # ok — reference
}