Infrastructure as Code with Terraform
Complete guide to Infrastructure as Code using Terraform including state management, modules, providers, workspaces, and production best practices for DevOps teams.
# Infrastructure as Code with Terraform
Introduction
Infrastructure as Code (IaC) with Terraform enables teams to define, provision, and manage cloud infrastructure using declarative configuration files. Terraform supports multiple cloud providers (AWS, Azure, GCP, Kubernetes, and more) and treats infrastructure as software that can be version controlled, tested, and deployed through CI/CD pipelines.
This tutorial covers Terraform fundamentals, state management, modules, providers, variables, outputs, workspaces, remote backends, and production best practices for DevOps teams.
Learning Objectives
By the end of this tutorial, you will be able to:
- Install and configure Terraform with cloud providers
- Write Terraform configuration for infrastructure resources
- Manage Terraform state locally and remotely
- Create reusable Terraform modules
- Use variables, outputs, and data sources
- Manage multiple environments with workspaces
- Implement Terraform in CI/CD pipelines
- Apply Terraform best practices for security and reliability
Technical Background
Terraform Workflow
flowchart LR
Write[Write Configuration] --> Init[terraform init]
Init --> Plan[terraform plan]
Plan --> Apply[terraform apply]
Apply --> Destroy[terraform destroy]
subgraph Terraform CLI
Write
Init
Plan
Apply
Destroy
end
State[State File] <--> Init
State <--> Plan
State <--> Apply
Providers[Provider Plugins] --> Init
CloudAPI[Cloud Provider API] --> ApplyCore Terraform Commands
| Command | Description | |---------|-------------| | `terraform init` | Initialize working directory, download providers | | `terraform plan` | Preview changes before applying | | `terraform apply` | Apply changes to infrastructure | | `terraform destroy` | Destroy all managed resources | | `terraform fmt` | Format configuration files | | `terraform validate` | Validate configuration syntax | | `terraform state list` | List resources in state | | `terraform show` | Show state or plan details | | `terraform import` | Import existing resources | | `terraform workspace` | Manage workspaces |
Terraform State
State is a JSON file that tracks resources managed by Terraform. It maps configuration to real-world resources and is essential for planning and applying changes.
| State Aspect | Description | |--------------|-------------| | Local state | `terraform.tfstate` stored locally | | Remote state | Stored in S3, Azure Blob, GCS, etc. | | State locking | Prevents concurrent modifications | | State versioning | Enables rollback to previous state |
Prerequisites
- Cloud account (AWS, Azure, or GCP) with IAM permissions
- Terraform CLI installed (v1.5 or later)
- Understanding of cloud resource concepts
- Git repository for version control
- Remote state storage for team collaboration
Hardware and Software Requirements
| Component | Requirement | |-----------|-------------| | Operating System | Linux, macOS, or Windows | | Terraform | v1.5 or later | | Cloud CLI | AWS CLI v2, Azure CLI, or gcloud CLI | | IDE | VS Code with Terraform extension | | Storage | S3 bucket, Azure Blob Storage, or GCS bucket | | Database | DynamoDB or similar for state locking |
Step-by-Step Implementation
Phase 1: Install Terraform
# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform -y
# Verify
terraform versionPhase 2: Configure Provider
# main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}Initialize:
terraform initPhase 3: Define Resources
#### VPC and Networking
# vpc.tf
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index}"
}
}
data "aws_availability_zones" "available" {
state = "available"
}#### Compute Resources
# compute.tf
variable "instance_type" {
type = string
default = "t3.micro"
}
resource "aws_security_group" "web" {
name_prefix = "web-sg-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[count.index].id
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-server-${count.index}"
}
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}Phase 4: Variables and Outputs
# variables.tf
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 2
}
# outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "instance_public_ips" {
value = aws_instance.web[*].public_ip
}Phase 5: Plan and Apply
# Validate configuration
terraform validate
# Format configuration
terraform fmt
# Preview changes
terraform plan -var="environment=staging"
# Apply changes
terraform apply -var="environment=staging"
# Confirm with 'yes'
# Destroy infrastructure
terraform destroy -var="environment=staging"Phase 6: Create Reusable Modules
modules/
vpc/
main.tf
variables.tf
outputs.tf
ec2/
main.tf
variables.tf
outputs.tf# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr
enable_dns_hostnames = true
tags = {
Name = var.name
}
}
output "vpc_id" {
value = aws_vpc.this.id
}# Root configuration using module
module "vpc" {
source = "./modules/vpc"
cidr = "10.0.0.0/16"
name = "my-vpc"
}Phase 7: Workspaces for Multiple Environments
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# List workspaces
terraform workspace list
# Select workspace
terraform workspace select staging
# Apply with workspace-specific variables
terraform apply -var-file="environments/staging.tfvars"Phase 8: Terraform in CI/CD
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-role
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplanExpected Output
- Terraform configuration defining infrastructure resources
- State file tracking all managed resources
- Reusable modules for VPC, compute, and other components
- Multiple environments managed with workspaces
- CI/CD pipeline automating Terraform plan and apply
- Remote state with locking for team collaboration
Verification Procedures
| Check | Method | Expected Result | |-------|--------|-----------------| | Validate config | `terraform validate` | No errors | | Plan changes | `terraform plan` | Shows intended changes | | Apply infrastructure | `terraform apply` | Resources created successfully | | Check state | `terraform state list` | All resources tracked | | Workspace isolation | `terraform workspace list` | Multiple workspaces exist |
Troubleshooting Scenarios
| Issue | Diagnosis | Resolution | |-------|-----------|------------| | `terraform init` fails | Provider not found | Check provider source and version | | State lock error | Concurrent operation | Wait or force unlock | | Resource already exists | Created outside Terraform | Use `terraform import` | | Plan shows drift | Manual changes outside Terraform | Run `terraform refresh` | | Backend auth fails | Missing cloud credentials | Configure AWS CLI or env vars | | Module not found | Incorrect source path | Verify module path |
Best Practices
- **Version control everything:** Store Terraform configuration in Git
- **Use remote state:** Never store state locally for team projects
- **Enable state locking:** Prevent concurrent modifications
- **Pin provider versions:** Use version constraints
- **Separate environments:** Use workspaces or separate directories
- **Keep modules small:** Each module should do one thing well
- **Use data sources:** Reference existing resources
- **Validate before apply:** Always review `terraform plan`
Security Considerations
- **Never commit secrets:** Use environment variables or secret managers
- **Restrict provider permissions:** Least-privilege IAM policies
- **Encrypt state files:** Remote state backends should support encryption
- **Review plan output:** Check for unintended resource destruction
- **Use policy-as-code:** Implement Sentinel or OPA for compliance
- **Rotate state encryption keys:** Periodically rotate KMS keys
Conclusion
Terraform is a leading Infrastructure as Code tool that enables teams to manage cloud infrastructure declaratively. By following best practices for state management, module design, workspaces, and security, teams can provision infrastructure reliably and consistently across environments. Terraform's multi-provider support makes it ideal for multi-cloud and hybrid cloud scenarios.
References
- Terraform Documentation: https://developer.hashicorp.com/terraform/docs
- Terraform AWS Provider: https://registry.terraform.io/providers/hashicorp/aws/latest/docs
- Terraform Registry: https://registry.terraform.io/
- Terraform Best Practices: https://www.hashicorp.com/resources/infrastructure-as-code-best-practices
- Terraform State: https://developer.hashicorp.com/terraform/language/state