Introduction to Terraform
Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp. It allows you to define and provision infrastructure resources across multiple cloud providers using a declarative configuration language.
What is Infrastructure as Code?
Infrastructure as Code is the practice of managing infrastructure in a defined, repeatable manner. Instead of manually configuring resources through console interfaces, you write code to define your infrastructure.
Benefits of IaC:
- Version Control: Track infrastructure changes over time
- Reusability: Share and reuse infrastructure configurations
- Consistency: Eliminate manual configuration errors
- Scalability: Deploy infrastructure reliably at scale
Getting Started with Terraform
Installation
# macOS
brew install terraform
# Linux
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/
# Windows (using Chocolatey)
choco install terraformVerifying Installation
terraform versionTerraform Workflow
The Core Workflow
Terraform follows a simple three-step workflow:
- 1Write: Define infrastructure in HCL configuration files
- 2Plan: Preview changes before applying them
- 3Apply: Provision the infrastructure
Basic Commands
# Initialize a new Terraform project
terraform init
# Format and validate configuration
terraform fmt
terraform validate
# Preview changes
terraform plan
# Apply configuration
terraform apply
# Destroy infrastructure
terraform destroyHCL Syntax Basics
Terraform Configuration Structure
Terraform uses HashiCorp Configuration Language (HCL), a domain-specific language designed for configuration.
# Main configuration file
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Provider configuration
provider "aws" {
region = "us-east-1"
}
# Resource definition
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "terraform-example"
}
}Configuration Blocks
Terraform Block: Defines Terraform settings
terraform {
required_version = ">= 1.0"
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}Provider Block: Configures providers
provider "aws" {
region = "us-east-1"
profile = "default"
}Resource Block: Defines infrastructure resources
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-name"
tags = {
Name = "My Bucket"
Environment = "Dev"
}
}Core Concepts
Providers
Providers are plugins that Terraform uses to manage resources. They abstract away the API differences between cloud providers.
provider "aws" {
region = "us-east-1"
}
provider "azurerm" {
features {}
}
provider "google" {
project = "my-project-id"
region = "us-central1"
}Resources
Resources are the most important element in the Terraform language. Each resource block describes one or more infrastructure objects.
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}Variables
Variables make your configuration more dynamic and reusable.
# variables.tf
variable "region" {
description = "AWS region for resources"
type = string
default = "us-east-1"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "enable_monitoring" {
description = "Enable detailed monitoring"
type = bool
default = false
}Outputs
Outputs return values to the CLI and can be used by other Terraform configurations.
output "instance_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.example.public_ip
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}Local Values
Locals are named values that you can reference throughout your configuration.
locals {
common_tags = {
Project = "my-app"
Environment = "production"
ManagedBy = "terraform"
}
instance_names = ["web-1", "web-2", "web-3"]
}
resource "aws_instance" "web" {
count = 3
ami = var.ami_id
instance_type = var.instance_type
tags = local.common_tags
}Data Sources
Data sources allow you to fetch information about existing resources.
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}State Management
Understanding State
Terraform state is the mechanism by which it tracks the resources it manages. The state file contains a mapping between your configuration and real-world resources.
State File
{
"version": 4,
"terraform_version": "1.6.0",
"serial": 1,
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": [
{
"name": "example",
"provider": "provider["registry.terraform.io/hashicorp/aws"]",
"type": "aws_instance",
"instances": [
{
"attributes": {
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t2.micro"
}
}
]
}
]
}
]
}Remote State
For team collaboration, store state remotely:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}Dependency Management
Implicit Dependencies
Terraform automatically infers dependencies when one resource references another.
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # Implicit dependency
cidr_block = "10.0.1.0/24"
}Explicit Dependencies
Use depends_on for dependencies not visible to Terraform.
resource "aws_db_instance" "default" {
allocated_storage = 20
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
db_name = "mydb"
username = "admin"
password = var.db_password
depends_on = [aws_security_group.db]
}Provisioners
Provisioners execute scripts on the local machine or remote resource.
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t2.micro"
# File provisioner
provisioner "file" {
source = "app.conf"
destination = "/etc/app.conf"
}
# Remote-exec provisioner
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl start nginx"
]
}
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}Best Practices
1. Directory Structure
project/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── modules/
├── vpc/
├── ec2/
└── rds/2. Use Variables for Customization
# Good
resource "aws_instance" "web" {
instance_type = var.instance_type
ami = var.ami_id
}
# Avoid hardcoding
resource "aws_instance" "web" {
instance_type = "t2.micro"
ami = "ami-12345678"
}3. Tag Your Resources
locals {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
resource "aws_instance" "web" {
tags = local.tags
}4. Use Terraform Format
terraform fmt -recursive5. Validate Before Applying
terraform validate
terraform planCommon Patterns
Multiple Environments
# terraform.tfvars
environment = "production"
# main.tf
locals {
env_config = {
production = {
instance_count = 3
instance_type = "t2.large"
}
staging = {
instance_count = 1
instance_type = "t2.micro"
}
}
config = local.env_config[var.environment]
}
resource "aws_instance" "web" {
count = local.config.instance_count
instance_type = local.config.instance_type
}Count and For Each
# Count
resource "aws_instance" "web" {
count = 3
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "web-${count.index}"
}
}
# For each
resource "aws_instance" "web" {
for_each = toset(["web-1", "web-2", "web-3"])
ami = var.ami_id
tags = {
Name = each.value
}
}Troubleshooting
Common Issues
1. State Lock Issues
terraform force-unlock <LOCK_ID>2. Resource Already Exists
terraform import aws_instance.web i-1234567890abcdef03. Drift Detection
terraform plan -refresh-onlyNext Steps
Now that you understand Terraform basics, explore:
- Modules for reusability
- State management strategies
- CI/CD integration
- Multi-environment deployments
- Advanced resource configurations
Terraform basics provide the foundation for managing modern infrastructure as code. Master these concepts before moving to advanced topics like modules, state management, and CI/CD integration.