Terraform 1.5 added top-level `check` blocks. How do they differ from a `postcondition`, and when would you choose one over the other?
answer
- warning, not error
- top-level block with a label
- assert instead of condition alone
- its own scoped data source
- should this block the deploy?
basics
~20 sA failed postcondition is an error that stops the run; a failed assertion in a check block is a warning that does not. Check blocks are for non-blocking verification of things outside the module's control, so an unrelated problem cannot wedge the pipeline.
solid answer
~50 sA `check` block is a top-level block, added in Terraform 1.5, containing one or more `assert` blocks — each with the familiar `condition` and `error_message` — and optionally one scoped `data` block usable only inside that check. The decisive difference is severity: a failed `assert` produces a **warning**, so the plan or apply still succeeds, whereas a failed `postcondition` is an error that fails the run. Checks are also evaluated last, after everything else has been planned or applied, which is what makes "is the endpoint I just deployed actually answering" expressible. So the choice is about what should happen when the claim is false. If the run must not be allowed to succeed — a guarantee your module owes its callers — use a `postcondition`. If the claim concerns something outside your control, where a failure is information rather than grounds for blocking a deploy, use a `check`.
code
hcl · 10 linescheck "api_health" {
data "http" "api" {
url = "https://${aws_lb.app.dns_name}/healthz"
}
assert {
condition = data.http.api.status_code == 200
error_message = "The application health endpoint did not return 200 after apply."
}
}go deeper
Know that a check block exists and that its assertions produce warnings rather than errors, so seeing one fail does not mean the apply was blocked. Do not confuse it with the terraform validate command.
Be able to describe the anatomy: a top-level labelled block, one or more assert blocks with condition and error_message, and an optional scoped data source visible only inside the check.
Answer the real question — should a false claim block the deploy? Point out that checks are evaluated last and that even an unreadable scoped data source only warns, which is what makes them safe to aim at external systems.
Own the policy: which assertions are allowed to fail a production apply, which are advisory, and what mechanism actually routes a warning to a human. Non-blocking checks nobody reads are a governance illusion.
## The construct ```hcl check "api_health" { data "http" "api" { url = "https://${aws_lb.app.dns_name}/healthz" } assert { condition = data.http.api.status_code == 200 error_message = "The application load balancer health endpoint did not return 200." } } ``` Three things are new here. `check` is a **top-level** block with a label, not something nested in a resource. It may contain one or more `assert` blocks, each carrying the same `condition` and `error_message` pair used everywhere else in the custom-condition family. And it may contain a single **scoped data source** — a `data` block that exists only inside this check and is not addressable from the rest of the configuration. The `http` data source above comes from the hashicorp/http provider. ## The differences that matter **Severity.** This is the reason the feature exists. A failed `assert` is a warning. The plan or apply reports it and *succeeds*. A failed `postcondition` is an error: the run fails. Everything else follows from this. **Ordering.** Checks are evaluated after everything else has been planned or applied, so an assertion may legitimately depend on the result of the whole run rather than on one object. **Scoped data failures are also non-blocking.** If the scoped data source inside a check cannot be read at all — the endpoint is unreachable, the request times out — that too is reported as a warning rather than failing the run. A check therefore cannot break your pipeline, which is exactly the guarantee that makes it safe to point at things you do not control. **Attachment.** A postcondition belongs to one object and speaks about it. A check is standalone, has its own name, and can assert about the whole system. ## Choosing Ask one question: **if this claim is false, should the deployment be allowed to succeed?** If no — the module has promised something and cannot deliver it — that is a `postcondition`. "This module guarantees the bucket it returns has versioning enabled." Failing loudly is correct. If yes — the claim is useful information about the surrounding world — that is a `check`. "The health endpoint answered 200 after the deploy." A flaky third-party endpoint, a monitoring URL that is briefly down, a DNS record that has not propagated: none of these are reasons to fail an apply that did exactly what it was asked to do, and turning them into errors is how teams end up with a pipeline everyone reruns on reflex, which is worse than no check at all. The uncomfortable half of that tradeoff is honest to state: a warning is a thing people stop reading. A check is only worth adding if something actually surfaces it — a plan output somebody reviews, a CI step that greps for warnings, a continuous-validation run on a schedule. "We added checks" and "we know when they fail" are not the same claim. ## Where it sits among the rest By Terraform 1.5 the family is four constructs, and it is worth being able to lay them out: `validation` on the input, `precondition` on the assumption, `postcondition` on the promise, `check` on the observation. The first three are all blocking and all attached to something; the fourth is standalone and advisory. Being able to place the tools on that spectrum — and to say which failures deserve to stop a production apply and which merely deserve to be seen — is the judgment the question is testing, far more than the syntax.
- What happens if the scoped data source inside a check block cannot be read at all?It is reported as a warning, not an error, and the run still succeeds. That is deliberate: a check is meant to observe things outside the configuration's control, so an unreachable endpoint or a timed-out request must not be able to fail an apply that otherwise did exactly what was asked.
- If a check only produces a warning, how does anyone find out it failed?Only if something surfaces it — a plan output a reviewer reads, a CI step that fails or annotates on warnings, or a scheduled run whose job is to evaluate the checks. That is the honest cost of the non-blocking design: warnings are easy to stop reading, so adding checks without a route to a human is close to adding nothing.
- Lay out the whole custom-condition family in one sentence each.`validation` claims something about an input variable; `precondition` claims an assumption must hold before an object is handled; `postcondition` claims a guarantee about what was produced; `check` makes a standalone observation about the wider system. The first three are blocking errors attached to a specific object, the fourth is an advisory warning that stands alone.
saying these in an interview costs you the question
- Thinks a failed assert fails the plan or apply
- Believes check blocks replace postconditions entirely
- Puts a check block inside a resource or lifecycle block
- Assumes an unreadable scoped data source errors the run
- Adds checks with nothing in the pipeline reading warnings