Terraform with AWS: A Beginner's Guide
Introduction
Terraform is an Infrastructure as Code (IaC) tool that allows you to define and provision infrastructure using a declarative configuration language. This guide will help you learn how to use Terraform with AWS from scratch.
Prerequisites
Basic understanding of cloud concepts
AWS account
Command line familiarity
1. Setting Up Your Environment
Installing Terraform
On macOS (using Homebrew):
brew install terraform
On Windows (using Chocolatey):
choco install terraform
On Linux:
wget https://releases.hashicorp.com/terraform/1.7.5/terraform_1.7.5_linux_amd64.zip
unzip terraform_1.7.5_linux_amd64.zip
sudo mv terraform /usr/local/bin/
Verify the installation:
terraform version
Installing AWS CLI
On macOS:
brew install awscli
On Windows: Download and run the AWS CLI MSI installer from the AWS website.
On Linux:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
Configuring AWS Credentials
Run the following command and follow the prompts:
aws configure
You'll need to provide:
AWS Access Key ID
AWS Secret Access Key
Default region name (e.g., us-east-1)
Default output format (json)
2. Terraform Basics
Key Concepts
Provider: Plugin that allows Terraform to interact with a specific cloud provider (AWS in our case)
Resource: An infrastructure component (e.g., EC2 instance, S3 bucket)
Data Source: Used to fetch information about existing resources
Variables: Parameters that can be used throughout your configuration
Outputs: Values that are displayed after running Terraform and can be used by other configurations
Modules: Reusable components that encapsulate a set of resources
Terraform Workflow
Initialize:
terraform initPlan:
terraform planApply:
terraform applyDestroy:
terraform destroy(when you want to remove resources)
3. Your First Terraform Configuration
Create a new directory for your project:
mkdir terraform-aws-demo
cd terraform-aws-demo
Create a file named main.tf:
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
}
# Create a VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "terraform-demo-vpc"
}
}
# Create a subnet
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "terraform-demo-subnet"
}
}
Initialize Terraform:
terraform init
Plan your changes:
terraform plan
Apply your changes:
terraform apply
When prompted, type "yes" to confirm.
4. Using Variables
Create a file named variables.tf:
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
variable "subnet_cidr" {
description = "CIDR block for the subnet"
type = string
default = "10.0.1.0/24"
}
Update main.tf to use these variables:
provider "aws" {
region = var.region
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform-demo-vpc"
}
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
tags = {
Name = "terraform-demo-subnet"
}
}
5. Creating an EC2 Instance
Add the following to your main.tf:
# Create a security group
resource "aws_security_group" "allow_ssh" {
name = "allow_ssh"
description = "Allow SSH inbound traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
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"]
}
}
# Create an Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
}
# Create a route table
resource "aws_route_table" "r" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
}
# Associate subnet with route table
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.main.id
route_table_id = aws_route_table.r.id
}
# Create an EC2 instance
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI ID (update as needed)
instance_type = "t2.micro"
subnet_id = aws_subnet.main.id
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
associate_public_ip_address = true
tags = {
Name = "terraform-demo-instance"
}
}
# Output the public IP of the instance
output "instance_public_ip" {
value = aws_instance.web.public_ip
}
Note: You'll need to update the AMI ID with a valid and current one for your region.
6. Organizing Your Configuration
For larger projects, it's best to organize your Terraform code into separate files:
main.tf: Main configuration filevariables.tf: Variable declarationsoutputs.tf: Output definitionsterraform.tfvars: Variable values (not committed to version control when contains sensitive data)
Example outputs.tf:
output "vpc_id" {
value = aws_vpc.main.id
}
output "subnet_id" {
value = aws_subnet.main.id
}
output "instance_public_ip" {
value = aws_instance.web.public_ip
}
Example terraform.tfvars:
region = "us-west-2"
vpc_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
7. Using Terraform Modules
Modules allow you to create reusable components. Create a directory structure:
terraform-aws-demo/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── modules/
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
Example module (modules/vpc/main.tf):
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = var.vpc_name
}
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
tags = {
Name = "${var.vpc_name}-subnet"
}
}
Example module variables (modules/vpc/variables.tf):
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
}
variable "subnet_cidr" {
description = "CIDR block for the subnet"
type = string
}
variable "vpc_name" {
description = "Name of the VPC"
type = string
default = "terraform-demo-vpc"
}
Example module outputs (modules/vpc/outputs.tf):
output "vpc_id" {
value = aws_vpc.main.id
}
output "subnet_id" {
value = aws_subnet.main.id
}
Using the module in your main configuration:
module "vpc" {
source = "./modules/vpc"
vpc_cidr = var.vpc_cidr
subnet_cidr = var.subnet_cidr
vpc_name = "terraform-demo-module-vpc"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = module.vpc.subnet_id
tags = {
Name = "terraform-demo-instance"
}
}
8. State Management
Terraform keeps track of your infrastructure in a state file. By default, this is stored locally as terraform.tfstate. For team environments, you should use remote state storage:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "dev/terraform.tfstate"
region = "us-east-1"
}
}
This requires an S3 bucket to be created beforehand.
9. Best Practices
Version Control: Store your Terraform configurations in a version control system like Git
Remote State: Use remote state storage for team environments
State Locking: Prevent concurrent modifications to the same infrastructure
Workspaces: Use workspaces for managing multiple environments (dev, staging, prod)
Secrets Management: Don't hardcode sensitive information in your configuration
Modules: Use modules for reusable components
Tagging: Implement a consistent tagging strategy for your resources
10. Next Steps
Learn about Terraform Cloud for team collaboration
Explore more complex AWS resources (RDS, ECS, Lambda, etc.)
Implement a CI/CD pipeline for your Terraform code
Study Terraform's advanced features like dynamic blocks and functions