Terraform and configuration validation

Official Terraform Logo

The infrastructure-as-code (IaC) programming language Terraform is an amazing tool in cloud engineering, primarily for its declarative and cloud-agnostic nature. It provides cloud provisioning and maintenance capabilities that scale with your objectives, from local experimentation to full-on production development.

This article will highlight Terraform’s ability to validate variables when referencing the state file, before and after a Terraform apply, and throughout implementation.

Before we begin, let’s cover…

Important Resources for Terraform Developers

Whether you’ve never written in Terraform or you’ve built and managed a production-scale solution, these resources are important tools in your IaC kit.

Are you new to Terraform or seasoned and looking for a refresher?

I recommend HashiCorp’s Terraform documentation. There you will find tutorials, explanations, use cases, and instructions for everything to do with Terraform. For experienced developers, the documentation also covers intermediate and advanced capabilities.

Where can I find Terraform providers and modules?

Public Terraform providers and modules live in the Terraform registry. There you will find providers for the leading cloud providers (think AWS, Azure, and GCP) and modules that provide the building blocks for cloud and SaaS services (think IAM policies or Kubernetes engines).

Configuration Validation Methodology

Let’s start with variables, a key aspect of dynamic referencing in Terraform. For Terraform’s official page covering configuration validation, please reference the following article.

What is variable validation in Terraform?

The most common Terraform directory structure includes a variables.tf file that stores variables. Within that file are variable blocks which contain a “validation” meta-argument to ensure variables fit your desired structure or range. There are two required arguments: condition and error_message. The former provides a statement that the variable must conform to, and the latter provides custom reasoning for why the validation failed. The key part of the argument is that if the condition isn’t met, Terraform will “fail” and prevent operations. Let’s introduce a use case to explain the feature better:

Say a development team is building an AWS AgentCore browser agent and wants to ensure the agent’s LLM is in Europe for data residency compliance purposes. They’ve decided to stick to Anthropic models, and so they configure their variable block like so:

variable "bedrock_model_id" {
  description = "Bedrock model ID for the AgentCore browser agent."
  type        = string
  default     = "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"

  validation {
    condition     = startswith(var.bedrock_model_id, "eu.anthropic")
    error_message = "For data compliance purposes, the Bedrock Anthropic model must be local to EU"
  }
}

Here, the condition variable checks if the Bedrock model ID input starts with “eu.anthropic” and explains why the model ID must conform to the condition. With this, global, non-European, and non-Anthropic models will not pass validation, preventing Terraform Cloud operations. It essentially acts as a hard checkpoint that avoids potential consequences if left unchecked. This doc covers more information on regional vs. global model IDs.

This argument is very flexible. You can limit the number of subnets in a VPC, restrict nodes or clusters to a certain range, and designate a machine learning model to specific families or compute power.

Next, let’s cover how Terraform applies this same logic to resource, data, and output blocks.

What are preconditions and postconditions in Terraform?

Preconditions and postconditions are meta-arguments that prevent Terraform operations. Preconditions apply to resource, data, and output blocks, whereas postconditions apply only to resource and data blocks. The two meta-arguments are nearly identical to the validation meta-argument in variable blocks, except that, within resource and data blocks, a lifecycle meta-argument wraps both conditions. Between the two conditions, the differences lie in their order in the Terraform workflow and use cases. Preconditions occur during the plan phase, and postconditions occur during/after the apply phase. The following use cases illustrate the implementation.

A development team wants to keep their Terraform-managed EC2 instances affordable by capping the instance’s memory. To do this, they use a data source to look up those attributes and enforce the limit via a precondition:

data "aws_ec2_instance_type" "selected" {
  instance_type = var.instance_type
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    precondition {
      condition     = data.aws_ec2_instance_type.selected.memory_size <= 4096
      error_message = "For cost control, instances must have 4 GiB of memory or less."
    }
  }
}

The precondition ensures the memory does not exceed 4 GiB, blocking larger, more expensive instances. Critically, the precondition’s scope enables the condition to obtain the data block’s attribute. When applied to data and output blocks, preconditions also ensure that referenced infrastructure attributes and Terraform outputs are valid before they are referenced or displayed, respectively.

When referenced data/resource attributes can only be determined after an apply or there are consequential dependencies, postconditions are a great option. Consider the following use case.

resource "aws_db_instance" "main" {
  identifier              = "main-db"
  engine                  = "postgres"
  allocated_storage       = 100
  backup_retention_period = 7

  lifecycle {
    postcondition {
      condition     = self.backup_retention_period >= 7 && self.backup_window != null
      error_message = "Database must have 7+ day backups configured before dependent services launch."
    }
  }
}

resource "aws_ecs_service" "api" {
  depends_on = [aws_db_instance.main]
  # ECS service won't launch if DB backup config is wrong
}

During an apply operation, Terraform creates a dependency graph based on implicit and explicit dependencies to determine the provisioning order. In the above case, the explicit dependency created by the “depends_on” meta-argument in the aws_ecs_service.api resource means that the aws_db_instance.main resource will be created before the aws_ecs_service.api resource. However, if the postcondition, which checks for a configured database backup set to 7+ days, fails, the apply operation will fail before provisioning the ECS service. This ensures that costly or harmful configurations don’t cascade into their dependencies.

What are check blocks in Terraform?

Finally, the check block provides infrastructure validation warnings, not failures, after a plan or an apply operation. Check block use cases include checking general Terraform behavior, configuration verification, and broad validation. The key to this feature is that instead of blocking a plan or an apply operation, check blocks output a warning without interruptions. Official Terraform documentation provides a great example:

check "health_check" {
  data "http" "terraform_io" {
    url = "https://www.terraform.io"
  }

  assert {
    condition = data.http.terraform_io.status_code == 200
    error_message = "${data.http.terraform_io.url} returned an unhealthy status code"
  }
}

Here, if the status_code for Terraform’s page is unhealthy (not 200), Terraform will output a warning without interrupting operations.

What else should I know about Terraform’s validation methods?

There are two important behaviors: the order of operations and the scope.

In what order of operations do these validation methods occur?

An official Terraform infographic showcasing the order of operations of the validation methods

The above diagram wonderfully describes the order of operations, with variable validation at the beginning and checks at the end. The infographic also highlights the position of the validation methods in the Terraform workflow.

Does the input method for a variable affect its validation?

No! Whether your variable is sourced from the CLI, a .tfvars file, variable defaults, or environmental variables, variable validation applies to all of them.

How can I take my Terraform configuration to another level?

Configuration Validation is one step in the stairway to a production-ready Terraform configuration. Securing secrets is another step, and the following Terraform documentation covers just that.

Summary

In short, Terraform offers a plethora of methods to validate configuration at every step of the Terraform workflow. These methods protect your infrastructure against consequential problems in a cloud ecosystem, ensure configuration security, and provide a better experience when transferring the codebase to clients. If you frequently use Terraform, try these validation methods. You won’t regret it.