Vars
A var "<name>" { ... } block resolves a string at config-load time from an environment variable, the HEAD commit of a local git checkout, or a literal fallback, and makes it referenceable as ${var.<name>}. Vars are evaluated before resources, so the resolved value is just another string by the time a resource attr reads it.
var "app_tag" {
from_env = "APP_TAG"
from_git_sha = "../app"
default = "dev"
}
resource "docker_container" "app" {
host = host.primary.addr
image = "app:${var.app_tag}"
pull = false
}
var_block ::= "var" string "{" attr* "}"
The single label is the var's name. Names must be unique within the document — duplicates across -c files error with duplicate var.
The problem it solves: mutable tags defeat the diff
Stratum decides whether to recreate a docker_container by diffing its desired attrs against the last applied state. A locally-built image tagged app:latest defeats this: rebuilding the image moves what latest points to, but the container's image = "app:latest" attr never changes, so the plan diff sees a NoOp and the container keeps running the stale image. The only escape was stratum apply -r to force a recreate — blunt, and with its own ordering pitfalls.
A var sourced from the build's git SHA fixes this declaratively. Derive an immutable tag from the source checkout's HEAD and reference the same ${var.app_sha} in both the build command and the container image:
var "app_sha" {
from_git_sha = "../app"
from_env = "APP_SHA"
}
resource "ssh_exec" "app-build" {
host = host.primary.addr
command = "cd /srv/repos/app && docker build -t app:${var.app_sha} ."
depends_on = ["ssh_exec.app-pull"]
}
resource "docker_container" "app" {
host = host.primary.addr
image = "app:${var.app_sha}"
pull = false
depends_on = ["ssh_exec.app-build"]
}
A new commit moves HEAD, so ${var.app_sha} resolves to a new value, so both the command and the image attrs change. The normal plan diff then re-runs the build and recreates the container in dependency order, in a single apply — no -r, no mutable :latest. The pull = false is required because the image is built on the host and isn't in any registry (see docker_container: pull = false).
Sources
At least one of from_env, from_git_sha, or default is required. A block with none of them is a hard error (BadVarBody).
| attr | type | description |
|---|---|---|
from_env | string | Name of an environment variable. Used when set to a non-empty value; an unset or empty value falls through to the next source. |
from_git_sha | string | Path to a local git checkout. Runs git -C <path> rev-parse [--short] HEAD and uses the resulting SHA. Relative paths resolve against the .strat file's directory (same rule as secret from_file). A failed git invocation falls through to the next source. |
short | bool | Default true. Controls the SHA width for from_git_sha: true passes --short (7-ish chars), false uses the full 40-char SHA. No effect on the other sources. |
default | string | Literal fallback used when no earlier source yields a value. |
Resolution precedence
Sources are tried in a fixed order and the first one that yields a value wins:
from_env— if the named env var is set and non-empty.from_git_sha— ifgit rev-parsesucceeds and returns a hex SHA.default— the literal fallback.
A source that is specified but yields nothing (env var unset or empty, git checkout missing or rev-parse failing) falls through to the next source rather than erroring. If no source yields a value and there is no default, resolution is a hard error and the load aborts — vars are fail-closed:
var `app_sha`: could not resolve — no source yielded a value (tried: from_env `APP_SHA`, from_git_sha `../app` (...))
fix: set a working `from_env`/`from_git_sha`, or add a `default = "..."`
This means from_env overrides the git SHA — useful in CI, where the pipeline already knows the exact commit it pushed and can pass it directly (APP_SHA=<sha> stratum apply -y) without relying on a local checkout being present.
from_git_sha tracks committed HEAD only
git rev-parse HEAD reads the committed tip of the current branch. A dirty working tree — uncommitted edits, staged-but-uncommitted changes — does not affect the resolved SHA. The var only moves when you commit.
This matters for the git-SHA tag pattern above. If the build host pulls the source with git reset --hard origin/main (the usual idempotent-checkout shape), then the tag stratum builds is keyed off the host's origin/main, while the tag stratum references is keyed off your local HEAD. For the two to match:
Contract: commit and push before applying. Your local HEAD must equal
origin/main(i.e. pushed), or the built tag won't match the referenced tag and the container will fail to start on a missing image.
References
${var.<name>} is a normal string interpolation (see String interpolation). It works anywhere a ${...} placeholder is allowed — string attrs and string values inside maps and lists — and the resolved value is always a string:
command = "docker build -t app:${var.app_sha} ."
image = "app:${var.app_sha}"
env = { BUILD_TAG = "${var.app_sha}" }
The reference form is exactly two segments: var.<name>. There is no field access — ${var.app_sha.foo} is too short/long and errors. An unknown name errors with unknown var:
unknown var `nope` in reference `var.nope`
Like host and secret blocks, var bodies are literal-only: any reference inside a var body (default = host.h.addr, ${var.other}, etc.) errors with references not allowed inside \var` blocks`. Vars are leaves — they cannot depend on hosts, secrets, or each other.
Vars vs secrets
Both resolve a string from the environment at load time, but they are not interchangeable:
- A var is plain config. Its value is printed in plan output and stored verbatim in state. Use it for non-sensitive values: image tags, build args, environment names.
- A secret is confidential. Its value is redacted from CLI output and state behind a marker. Use it for passwords, tokens, keys. See Secrets.
There is no from_git_sha on secrets and no redaction on vars. Pick by sensitivity.
Errors
| condition | error |
|---|---|
None of from_env / from_git_sha / default specified | BadVarBody |
No source yields a value and there is no default | VarUnresolved |
| Any reference inside the var body | RefInVarBlock |
Unknown var name in a ${var.x} reference | UnknownVar |
Duplicate name across -c files | DuplicateVar (names both paths) |
See also
docker_containerdrift on--refresh— the secondary safety net for the:latestcase when you haven't moved to SHA tags.- String interpolation — the
${...}grammar that${var.x}plugs into.