Terraform is an open-source Infrastructure as Code (IaC) tool that lets you define, provision, and manage cloud infrastructure using declarative configuration files instead of manual clicking through cloud consoles. It supports AWS, Azure, Google Cloud, and 300+ other providers, making it a vendor-agnostic way to automate infrastructure deployment.
Infrastructure as Code means treating your cloud resources—servers, databases, load balancers, security groups—the same way developers treat application code. Instead of manually provisioning resources through a web UI, you write configuration files that describe what infrastructure you want. Version control them. Review changes before applying them. Reproduce identical environments reliably.
This approach eliminates configuration drift (when real infrastructure diverges from documentation), reduces human error, and makes disaster recovery straightforward. You can spin up a complete production environment in minutes rather than hours.
Terraform operates in three main steps: write, plan, and apply.
You create .tf files using Terraform's HashiCorp Configuration Language (HCL). These files declare the resources you want. HCL is human-readable—it's not JSON or YAML, though Terraform can parse those too. A basic example:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "MyWebServer"
}
}
This declares a single AWS EC2 instance with a specific AMI and instance type. Simple and declarative—you describe the desired end state, not the steps to reach it.
Run terraform plan and Terraform fetches your current infrastructure state, compares it against your configuration, and shows exactly what it'll create, modify, or destroy. This is your safety net. Review the plan before you commit to changes. You'll see output like:
Terraform will perform the following actions:
# aws_instance.web_server will be created
+ resource "aws_instance" "web_server" {
+ ami = "ami-0c55b159cbfafe1f0"
+ instance_type = "t3.micro"
+ tags = {
+ "Name" = "MyWebServer"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Run terraform apply to execute the plan. Terraform calls the appropriate APIs (AWS, Azure, etc.) to create or modify resources. It tracks everything in a state file (terraform.tfstate), which is crucial—Terraform uses this file to know what resources exist and what changes are needed next time.
Providers are plugins that translate your Terraform code into API calls for specific platforms. AWS, Azure, Google Cloud, Kubernetes, GitHub, Datadog—each has a provider. You declare which providers you need:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Resources are the actual infrastructure components you're managing. An EC2 instance, S3 bucket, RDS database, security group—these are all resources. You define them with the resource block and give them a type and logical name.
Terraform maintains a state file that tracks the real-world resources it's managing. This is how it knows what exists, what changed, and what to destroy. In production, you store this state remotely (on S3, Terraform Cloud, etc.) so your team shares the same state and it's backed up.
Modules let you package reusable Terraform code. Instead of repeating the same resource definitions across projects, you create a module (a directory with .tf files) and call it with different variables. This follows the DRY principle and makes your infrastructure code maintainable.
Reproducibility: Run the same configuration in dev, staging, and production. Get identical results every time.
Version Control: Track infrastructure changes in Git. See who changed what, when, and why. Rollback if needed.
Collaboration: Team members review infrastructure changes before they're applied, just like code reviews.
Speed: Provision complex multi-resource environments in seconds. No more waiting for manual setup.
Multi-Cloud: Write once, deploy to AWS, Azure, and GCP. Reduce vendor lock-in risk.
Documentation: Your code is your infrastructure documentation. It's always current because changes require code updates.
Download Terraform from terraform.io or use a package manager:
# macOS
brew install terraform
# Ubuntu/Debian
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
Verify installation:
terraform version
Create a directory and add a file called main.tf:
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-name-12345"
}
resource "aws_s3_bucket_versioning" "example" {
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
This creates an S3 bucket with versioning enabled. Initialize Terraform:
terraform init
This downloads the AWS provider and sets up your working directory. Then plan:
terraform plan
Review the output. If it looks right, apply:
terraform apply
Type yes when prompted. Terraform creates your S3 bucket and saves state. To clean up:
terraform destroy
Most teams use a workflow like this: create a feature branch for infrastructure changes, write or modify .tf files, run terraform plan and commit the plan output to your PR for review, get approval, then run terraform apply in your main branch. This gives you code review discipline around infrastructure, not just applications.
For larger organizations, tools like Terraform Cloud or Atlantis automate this workflow further, running plans automatically on PRs and handling approvals.
Never commit your terraform.tfstate file to Git. It contains secrets and sensitive data. Store state remotely using S3 with encryption and locking, Terraform Cloud, or another backend.
Use sensitive = true on outputs containing passwords or keys. Use AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault for actual secrets—don't hardcode them in your configuration.
Be consistent with naming conventions. Include environment (dev, staging, prod) in resource names. Use variables for environment-specific values rather than duplicating configurations.
Always run terraform plan before applying. Use terraform fmt and terraform validate to catch errors. Consider tools like Terratest for automated infrastructure testing.
CloudFormation: AWS-only, more verbose, but deeply integrated with AWS. Terraform is more flexible and multi-cloud.
Ansible: Imperative (step-by-step instructions) rather than declarative. Better for configuration management than infrastructure provisioning, though it can do both.
Pulumi: Newer alternative using actual programming languages (Python, Go, JavaScript) instead of HCL. More powerful but steeper learning curve.
Terraform's sweet spot: easy to learn HCL syntax, massive ecosystem, true multi-cloud support, and a huge community.
Yes. Use terraform import to bring existing resources under Terraform management. You'll need to write the resource blocks to match what exists, then import them. It's tedious for large estates but doable. Alternatively, start fresh with new resources in Terraform.
Terraform will detect the drift on your next terraform plan. It'll show what changed and offer to restore the resource to match your configuration on the next apply. This is why state files matter—they track the expected state and catch deviations.
Yes. Terraform CLI is open-source and free forever. Terraform Cloud (managed hosting, teams, remote state) has a free tier for individuals and paid plans for teams. You only pay cloud providers for actual resources you provision, not Terraform itself.
Use separate state files for each environment, typically in separate directories or workspaces. Define variables for environment-specific values (instance types, instance counts, etc.). Consider using modules to keep code DRY. Many teams store each environment's config in a different directory: terraform/dev/, terraform/staging/, terraform/prod/.