
Infrastructure as Code makes deployments fast and repeatable, but it doesn't tell you whether what you deployed is actually correct, secure, and compliant. That's a separate problem, and it's the one validation tools solve. This post looks at how Terraform's native check blocks fit into that picture, alongside the linters, scanners, and policy engines you'd pair them with.
Catching misconfigurations and compliance gaps before they ship is the whole point of validation. Terraform's tooling for this has grown up in stages:
terraform validate).check blocks for in-configuration assertions.The trend is to validate earlier, and to keep validating after deployment rather than only at provisioning time.
check BlocksA check block is a native Terraform feature for writing custom assertions about your infrastructure. The point is to verify your assumptions on an ongoing basis, not only when resources are first provisioned.
check blocks can assert conditions across multiple resources or external systems, unlike resource-specific custom conditions. This allows for end-to-end, system-level validation.terraform plan and terraform apply, providing continuous feedback. This is further enhanced by features like HCP Terraform's health assessments for scheduled checks.check BlockA check block has a local name and contains one or more assert blocks.
check "descriptive_local_name" {
# One or more assert blocks
assert {
condition = # boolean expression
error_message = # string displayed if condition is false
}
}**condition**: A boolean expression. If true, the assertion passes. If false, it fails.error_message: A clear message explaining the failure, supporting interpolation for dynamic values.Example 1: Validating TLS Certificate Status
Ensure an AWS ACM certificate is in the ISSUED state.
resource "aws_acm_certificate" "cert" {
domain_name = "myapp.example.com"
validation_method = "DNS"
# ... other configurations
}
check "certificate_is_issued" {
assert {
condition = aws_acm_certificate.cert.status == "ISSUED"
error_message = "The certificate for ${aws_acm_certificate.cert.domain_name} is not ISSUED. Current status: ${aws_acm_certificate.cert.status}."
}
}Example 2: Verifying Service Endpoint Responsiveness
Use a scoped http data source to check if a service endpoint returns a 200 OK status.
resource "aws_lb" "app_lb" {
# ... load balancer configuration ...
name = "my-app-lb"
}
check "service_endpoint_health" {
# Scoped data source, only visible within this check block
data "http" "health_check" {
url = "https://${aws_lb.app_lb.dns_name}/health"
# In production, ensure you have valid certificates and set insecure = false
# For this example, we might use insecure if it's an internal or test endpoint
insecure = true
# Ensure this data source is queried only after the LB is available
depends_on = [aws_lb.app_lb]
}
assert {
condition = data.http.health_check.status_code == 200
error_message = "Service endpoint at ${data.http.health_check.url} did not return HTTP 200. Status: ${data.http.health_check.status_code}."
}
}Note on Scoped data Sources: Data sources defined inside a check block are "scoped" to that block. If a provider error occurs during data retrieval (e.g., network issue), it's masked as a warning, aligning with the non-blocking nature of check blocks. The depends_on meta-argument is crucial for ensuring data sources are queried only after dependent resources are created/updated.
check blocks are evaluated at the end of terraform plan and terraform apply.apply (e.g., a new VM's IP), Terraform issues a warning that the result is "known after apply." The actual outcome is reported post-apply.error_message.HCP Terraform Plus Edition enhances this with "health assessments," automating check block evaluations and providing notifications, which is a step towards proactive governance.
check Blocks vs. Other Native ValidationTerraform has several native validation mechanisms, and they don't overlap as much as you might expect. Here's how check blocks line up against the others:
| Feature | Scope of Validation | Execution Point | Behavior on Failure | Typical Use Cases | Provider Interaction |
|---|---|---|---|---|---|
terraform validate |
Static config files (syntax, schema) | CLI command (before plan/apply) |
Error & Halt | Syntax checking, basic type validation | No |
| Input Variable Validation | Individual input variable values | During variable processing (before plan) |
Error & Halt | Ensuring variable format/constraints (regex, length) | No |
Resource precondition |
State/attributes for a single resource | Before resource operation (create/update/delete) | Error & Halt | Gatekeeping resource changes | Yes (resource data) |
Resource postcondition |
Outcome of a single resource operation | After resource operation (create/update/delete) | Error & Halt | Verifying immediate result of a resource change | Yes (resource data) |
check Block |
Entire config, inter-resource relations, external systems | End of plan/apply; HCP Health Assessments |
Warning & Continue | Ongoing health checks, operational states, integrations, compliance assertions | Via scoped data sources |
Each row has a job it's suited to. check blocks trade enforcement for breadth: they warn rather than block, but they can look across resources and external systems in a way the other mechanisms can't. When you need a change actually stopped, you reach for policy-driven enforcement instead.
Native features only go so far. Most teams pair them with a few external tools:
terraform validate: The foundational static analysis check.validate might miss.The tools themselves are capable. The hard part is wiring them into every pipeline and keeping them applied the same way across dozens of projects and teams. That's where an automation and management platform like Scalr helps: it gives you one place to define policy, enforce it, and see what's passing or failing, instead of configuring each tool per workspace.
check Blocks:
terraform fmt, validate, IDE integration, pre-commit hooks with linters/scanners.plan review (including check warnings), apply review.check block evaluation.error_messages.conditions focused.check blocks thoroughly.check blocks with your IaC.check block warnings, for example pausing for review or firing an alert. A platform with built-in execution environments takes most of the manual wiring off your plate here.check blocks are a solid addition to the validation toolkit. They give you an HCL-native way to assert things about your infrastructure's state, and they run with every plan and apply without any extra machinery.
They aren't the whole story, though. A real validation strategy combines native Terraform features with linters, security scanners, and a policy-as-code engine, and it treats those checks as code you keep refining as you learn what actually breaks in production.
At scale the harder problem isn't picking the tools. It's governance: applying policy the same way everywhere, seeing across all your environments at once, and not drowning in the upkeep of the validation pipelines themselves. An IaC management platform exists to handle that layer, so a multi-tool validation strategy stays consistent instead of fragmenting per team.
Concretely, that visibility takes the form of fleet-level reporting. Scalr is a drop-in Terraform Cloud alternative, and its reports treat drift, stale workspaces, and resource, module, and provider inventories as queryable objects across the whole fleet. A platform team can see where a check warning is going unaddressed and step in, instead of discovering it workspace by workspace.
