Inside a Terraform resource's `lifecycle` block, what do `precondition` and `postcondition` do, and when is each one evaluated?
answer
- assert an assumption, fail with your message
- before create or update, versus after
- only one of them may use self
- checked in plan when values are known
- the object already exists when it fails
basics
~20 sBoth assert something must be true and fail the run with your own error message if it is not. A precondition is checked before Terraform creates or updates the resource; a postcondition is checked afterwards and can inspect the resource's own attributes through self.
solid answer
~50 sEach is a block inside `lifecycle` with a `condition` expression and an `error_message`, available since Terraform 1.2. A `precondition` guards an assumption the resource depends on and is checked before the resource is created or updated — for example that the AMI a data source found has the architecture this instance type needs. A `postcondition` is checked after the resource has been created or updated, and it is the only one of the two that may use `self` to refer to the resource's own attributes, so it can assert on values that were unknown at plan time. Terraform evaluates conditions during plan wherever the values are already known, and defers the rest to apply. A failure is a hard error with your message, not a warning — and when a postcondition fails, the object has already been created and recorded in state, so you are diagnosing something that exists rather than preventing it. They turn silent wrong assumptions into named failures.
code
hcl · 16 linesresource "aws_instance" "app" {
ami = data.aws_ami.app.id
instance_type = "t3.small"
lifecycle {
precondition {
condition = data.aws_ami.app.architecture == "x86_64"
error_message = "The AMI selected for this instance must be x86_64."
}
postcondition {
condition = self.private_ip != ""
error_message = "The instance must have received a private IP address."
}
}
}go deeper
Know that both blocks hold a condition and an error_message, and that a false condition stops the run with that message instead of a raw provider error.
Explain the timing difference — precondition before create or update, postcondition after — and that only the postcondition may refer to the resource's own attributes through self.
Show the operational consequence: conditions run at plan time when values are known and are deferred to apply otherwise, so a failing postcondition leaves a created object in state, and assumptions worth blocking on belong in a precondition.
Decide where assumptions should be enforced across the estate — module preconditions, variable validation, advisory check blocks, or pipeline policy — so that failures land on the team that can fix them, with messages that say what to do.
## What they are `precondition` and `postcondition` are custom condition blocks that live inside a resource's or data source's `lifecycle` block. Each has exactly two arguments: ```hcl lifecycle { precondition { condition = data.aws_ami.app.architecture == "x86_64" error_message = "The selected AMI must be x86_64 to match this instance type." } } ``` If `condition` evaluates to false, Terraform stops and reports your `error_message`. There is no advisory mode: a failed condition fails the run. They are the one part of the lifecycle block that takes arbitrary expressions, because unlike the booleans they do not shape the plan graph — they only decide whether to proceed. ## The two, and their timing **Precondition** — checked *before* Terraform creates or updates the object. Its job is to state an assumption the resource depends on and to fail early, with a message a human wrote, instead of failing later inside the provider with an opaque API error. Typical assumptions: a looked-up image matches the architecture of the instance type; a subnet is in the availability zone this resource needs; two input values that must agree actually agree; a CIDR is big enough for the number of subnets being carved out of it. A precondition can reference anything in the configuration *except* the resource's own `self`, which does not exist yet. **Postcondition** — checked *after* the object has been created or updated. It can use `self` to refer to the resource's own attributes, which is what makes it useful: it asserts on values that were unknown when the plan was made. A postcondition on a data source is a particularly clean pattern, because it validates fetched data before anything else consumes it. ## Plan time versus apply time Terraform evaluates conditions as early as it can. When every value in the expression is already known at plan time, the check runs during `terraform plan` and the plan itself fails — the cheapest possible outcome, because nothing has changed yet. When the expression depends on something unknown until apply, evaluation is deferred, and the failure surfaces mid-apply. That gives the important operational caveat for postconditions: by the time one fails, the object has been created and Terraform has recorded it in state. The run errors out afterwards, so you are left with a real object plus a failed apply. A postcondition is therefore a detector and a stop signal, not a preventer — if the point is to stop something being built at all, the assumption belongs in a precondition, where it can be caught while the values are still known. ## Where they sit among the alternatives - Against an input variable's own value, a `validation` block on the variable is the closer fit, because it fails at the boundary and names the input. Preconditions earn their place when the assertion is about a *relationship* — between an input and a looked-up value, or between two resources — which a single variable cannot see. - Against continuously monitored assertions that should report rather than block, Terraform 1.5 added `check` blocks, which produce warnings instead of failing the run. Reach for those when "this should be true" is a health statement rather than a precondition for building. - Against the other lifecycle arguments, conditions are orthogonal. They do not affect ordering (`create_before_destroy`), deletion (`prevent_destroy`), or which attributes are diffed (`ignore_changes`). ## What good use looks like The habit that pays off is writing the error message for the person who will hit it at 2am: name the input, the looked-up value and what to do, not just "invalid configuration". A condition whose message reads "the AMI selected by var.ami_filter is arm64, but instance_type t3.small requires x86_64" turns a confusing provider error into a one-line fix. The failure mode to avoid is decorating every resource with conditions restating what the provider would reject anyway — that adds noise without adding a better message. Add them where an assumption is real, implicit, and easy to break by editing something far away.
- Why can a precondition not use self while a postcondition can?Because a precondition runs before the object is created or updated, so there are no attribute values to refer to. A postcondition runs afterwards, when the object exists and Terraform has its attributes, which is exactly why it is the right place to assert on values that were unknown at plan time.
- If a postcondition fails during apply, what is the state of the resource?It exists. Terraform created or updated the object and recorded it in state, then evaluated the condition and failed the run. You are diagnosing something real rather than preventing it — which is the argument for putting an assumption in a precondition whenever the values are known early enough.
- When would you use a check block instead of a postcondition?When the assertion is a health statement rather than a gate. `check` blocks, added in Terraform 1.5, report warnings without failing the run, so they suit continuous assertions about an environment. A postcondition is for something that must be true for this apply to be considered correct.
saying these in an interview costs you the question
- Thinks a failed condition is a warning rather than an error
- Expects a postcondition to prevent the resource being created
- Tries to reference self inside a precondition
- Assumes all conditions are evaluated only at apply time
- Uses them to restate validation the provider already performs