precondition, postcondition and check Blocks
Scenario
You want to catch errors at three different levels: before a resource is created (bad assumptions), after it's created (unexpected cloud behavior), and as an ongoing health check on every apply. Terraform provides three mechanisms for this — each with different failure behavior.
- Add a precondition inside a resource's lifecycle block that validates an assumption before the resource is created.
- Add a postcondition using self that checks a property after the resource is created.
- Add a standalone check block with an assert that runs after every apply — failures produce warnings, not errors.
- Observe the different failure behavior of each mechanism.
Additional Context
| Mechanism | When | Failure Impact |
| precondition | Before resource apply | Blocks apply (error) |
| postcondition | After resource apply | Blocks apply (error) |
| check block | After every apply | Warning only (apply succeeds) |
Preconditions validate assumptions ("I expect this data source to return an x86_64 AMI"). Postconditions validate guarantees ("I expect this instance to have a public IP"). Check blocks monitor ongoing health ("I expect this endpoint to return HTTP 200").
main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "us-east-1" }
variable "environment" {
type = string
default = "dev"
}
variable "expected_region" {
type = string
default = "us-east-1"
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "app" {
bucket = "checks-demo-${var.environment}-${data.aws_caller_identity.current.account_id}"
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
lifecycle {
# ─── PRECONDITION: checked BEFORE the resource is created ───
# Validates an assumption about the infrastructure state.
precondition {
condition = data.aws_region.current.name == var.expected_region
error_message = "This config must be deployed to ${var.expected_region}, but the provider is configured for ${data.aws_region.current.name}."
}
# ─── POSTCONDITION: checked AFTER the resource is created ───
# Validates that the resource was created with expected properties.
# 'self' refers to the resource that was just created.
postcondition {
condition = self.bucket_regional_domain_name != ""
error_message = "Bucket was created but regional domain name is empty — unexpected."
}
}
}
# ─── CHECK BLOCK: runs after EVERY apply, failures are WARNINGS ───
# Terraform 1.5+. Does not block the apply.
check "bucket_has_tags" {
assert {
condition = length(aws_s3_bucket.app.tags) > 0
error_message = "Warning: The app bucket has no tags. All buckets should be tagged."
}
}
check "environment_is_valid" {
assert {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Warning: Environment '${var.environment}' is not a standard value."
}
}outputs.tf
output "bucket_name" { value = aws_s3_bucket.app.id }
output "bucket_domain" { value = aws_s3_bucket.app.bucket_regional_domain_name }
output "current_region" { value = data.aws_region.current.name }Workflow Commands
terraform init && terraform apply
# Test precondition failure — wrong expected region
terraform plan -var="expected_region=eu-west-1"
# Error: This config must be deployed to eu-west-1, but the provider is configured for us-east-1.
# Test check warning — non-standard environment
terraform apply -var="environment=testing"
# Warning: Environment 'testing' is not a standard value.
# (apply still succeeds — checks produce warnings, not errors)
terraform destroy│ Error: Resource precondition failed │ │ on main.tf line 34, in resource "aws_s3_bucket" "app": │ 34: condition = data.aws_region.current.name == var.expected_region │ │ This config must be deployed to eu-west-1, but the provider │ is configured for us-east-1.

