Terraform: Infrastructure as Code for Cloud Automation
Infrastructure as Code (IaC) manages cloud resources through declarative configuration files rather than manual CLI commands or console clicks. Terraform, developed by HashiCorp, is the most widely adopted IaC tool, supporting over 2,000 providers including AWS, Azure, GCP, Kubernetes, GitHub, Cloudflare, and Datadog. The 2024 State of DevOps Report found that organizations using IaC deploy 46x more frequently than those relying on manual provisioning.
Terraform uses HashiCorp Configuration Language (HCL), a declarative language designed to describe infrastructure in human-readable and machine-parseable syntax.
Why Infrastructure as Code
Version control — infrastructure changes are tracked, reviewed, and audited through the same Git workflow as application code. Every change has an author, timestamp, and commit message.
Reproducibility — the same configuration produces identical environments across development, staging, and production. Environment drift is eliminated because you rebuild from the same definitions.
Automation — no manual clicking through cloud consoles. New environments can be provisioned in minutes instead of days. CI/CD pipelines can create ephemeral environments for every pull request.
Self-documentation — the configuration files ARE the documentation. There is no stale wiki page or inaccurate spreadsheet — the Terraform code describes the exact state of your infrastructure.
Disaster recovery — recreate an entire production environment from a single terraform apply in a new region.
HCL Syntax and Basics
Every Terraform configuration has three foundational block types:
terraform {
required_version = ">= 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.60"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
---
provider "aws" {
region = var.aws_region
default_tags {
tags = {
ManagedBy = "Terraform"
Project = "ExcellentWiki"
}
}
---
resource "aws_s3_bucket" "storage" {
bucket = "excellentwiki-${var.environment}-assets"
force_destroy = var.environment == "production" ? false : true
---Resource blocks declare infrastructure components. The syntax is resource "provider_type" "local_name" — aws_s3_bucket specifies the type, storage is the name used for referencing this resource elsewhere in the configuration.
Core Workflow
The Terraform workflow follows three commands:
terraform init — downloads provider plugins and initializes the working directory. Must be run once per configuration directory, or whenever providers change.
terraform plan — compares the configuration to the current state and shows what will be created, modified, or destroyed. Always review the plan before applying in production environments.
terraform apply — executes the changes described in the plan. Terraform creates resources in dependency order, respecting implicit and explicit dependencies between resources.
# Typical workflow
terraform init
terraform fmt # Format code to canonical style
terraform validate # Check syntax and internal consistency
terraform plan -out=tfplan # Generate and save execution plan
terraform apply tfplan # Apply the saved planterraform destroy tears down all resources managed by the configuration. Use with caution — there is no confirmation prompt unless -auto-approve is passed.
State Management
Terraform tracks every resource it manages in a state file — a JSON mapping between configuration resources and real-world infrastructure objects. State enables Terraform to calculate diffs, detect drift, and delete resources correctly.
For individual use, local state works. For teams, remote state with locking is essential:
terraform {
backend "s3" {
bucket = "excellentwiki-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
}
---The DynamoDB table provides state locking — preventing concurrent operations from corrupting the state. S3 versioning preserves state history for recovery. AWS KMS encryption protects sensitive data stored in state.
Alternative backends include Azure Storage (with Blob lease locking), GCS (with object versioning), Terraform Cloud, HashiCorp Consul, and PostgreSQL.
Variables and Outputs
Variables make configurations reusable across environments:
variable "environment" {
description = "Deployment environment (dev, staging, production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
---
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
---
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.environment == "production" ? "t3.medium" : var.instance_type
tags = {
Name = "app-${var.environment}"
Environment = var.environment
}
---Outputs expose information for other configurations or human consumption:
output "instance_public_ip" {
description = "Public IP address of the application instance"
value = aws_instance.app.public_ip
sensitive = false
---Modules
Modules are reusable Terraform configurations packaged as a unit. The Terraform Registry hosts thousands of community and official modules for common patterns (VPC, RDS, Kubernetes cluster):
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.13.0"
name = "excellentwiki-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_dns_hostnames = true
tags = {
Environment = var.environment
}
---Create your own modules for internal service patterns. A well-designed module has clearly defined inputs (variables), outputs for composition, and documentation describing its purpose and constraints.
Workspaces
Workspaces manage multiple environments with the same configuration. Instead of separate directory structures, workspaces use the same root module but maintain separate state files:
terraform workspace new dev
terraform workspace new staging
terraform workspace new production
terraform workspace select production
terraform applyWorkspaces are appropriate for simple environment isolation. For complex organizations, directory-based environments (with separate backend configurations) provide stronger isolation and different access controls.
Terraform Best Practices
Use remote state with locking — never use local state for shared environments. State contains sensitive data (resource IDs, connection strings, possibly secrets). Use encryption and strict access controls.
Pin provider and module versions — version = "~> 5.60" allows patch updates but prevents major version upgrades that might break configurations. Run terraform init -upgrade explicitly when ready to upgrade.
Separate concerns — store configuration in a structured directory layout:
terraform/
├── environments/
│ ├── dev/
│ ├── staging/
│ └── production/
├── modules/
│ ├── compute/
│ ├── database/
│ └── networking/
└── global/
├── iam.tf
└── route53.tfFormat and validate in CI — run terraform fmt -check and terraform validate in every pull request. Run terraform plan during review and terraform apply only after approval.
Secrets management — do not store secrets in terraform.tfvars or output them in plan logs. Use HashiCorp Vault, AWS Secrets Manager, or encrypted variables. For databases, generate passwords with the random_password resource and store in Secrets Manager.
Use prevent_destroy for critical resources — add lifecycle { prevent_destroy = true } to databases, S3 buckets with long-term data, and IAM roles to prevent accidental deletion.
Testing Terraform Code
Testing infrastructure code is as important as testing application code:
terraform plan validation in CI — run terraform plan in every pull request and review the output for unexpected changes. Fail the CI check if the plan creates or destroys critical resources without explicit approval. Tools like Atlantis and Terrateam automate this workflow with pull request comments showing plan output.
Static analysis with Sentinel or Open Policy Agent — enforce policies before apply. Write rules like “all S3 buckets must have encryption enabled” and “EC2 instances must use approved types only.” Run policy checks in CI during the plan stage.
Integration testing — after terraform apply, run smoke tests to verify the application works: HTTP health checks, database connectivity, certificate validation. This catches configuration errors that pass syntax validation but fail in context.
Alternatives to Terraform
- Pulumi — use real programming languages (Python, TypeScript, Go, C#, Java) with full IDE support, type checking, and existing test frameworks
- AWS CDK — define infrastructure with TypeScript, Python, Java, or C#, compiled to CloudFormation templates with CloudFormation’s drift detection and StackSets for multi-account management
- OpenTofu — open-source Terraform fork (Linux Foundation, 2024) with backward compatibility and community-driven development
- Crossplane — Kubernetes-native infrastructure provisioning using kubectl; infrastructure defined as Kubernetes custom resources
- CloudFormation — AWS-native IaC with deep integration, drift detection, and StackSets for multi-account deployment
Terraform remains the most portable IaC tool — learn it once, use it across all cloud providers. OpenTofu offers a drop-in replacement for organizations that prefer an open-source governance model.
FAQ
What is the difference between Terraform and CloudFormation? Terraform is cloud-agnostic (works with AWS, Azure, GCP, Kubernetes, and dozens of other providers) and uses HCL syntax. CloudFormation is AWS-only and uses JSON/YAML. Terraform supports modules, workspaces, and richer expression language. CloudFormation integrates more deeply with AWS services (custom resources, drift detection, StackSets).
How do I manage secrets in Terraform state?
State files contain resource IDs and properties that may include sensitive information. Encrypt the state backend at rest (S3 SSE-KMS, Azure Storage Encryption), restrict access with IAM policies, and use sensitive = true on output values. Tools like terraform state rm remove sensitive resources from state when appropriate.
How do I import existing infrastructure into Terraform?
Use terraform import <resource_type>.<local_name> <resource_id>. For bulk imports, use tools like terraformer (Google) or terracognita to generate configuration from existing cloud resources. After import, run terraform plan to verify the configuration matches the real infrastructure.
Should I use Terraform workspaces or separate directories? Workspaces work well for simple environment differences (dev, staging, prod). Separate directories provide stronger isolation, independent backends, and different access control per environment. For enterprise use, prefer separate directories or Terraform Cloud workspaces with Sentinel policies.
Can Terraform manage Kubernetes resources? Yes. The Kubernetes provider creates namespaces, Deployments, Services, Ingresses, ConfigMaps, and Secrets. The Helm provider deploys Helm charts. The kubectl provider handles resources not yet supported by the Kubernetes provider. Many teams use Terraform for infrastructure provisioning and Helm/Flux for application deployment.
Cloud Computing Overview — CI/CD Pipeline Guide — Cloud Security Guide