Tutorial 3: Build Infrastructure
Learning Objectives
- Create your first Terraform configuration
- Understand basic Terraform workflow
- Learn about providers and resources
- Build actual cloud infrastructure
Prerequisites
- Terraform installed and verified
- Cloud provider account (AWS used in examples)
- Cloud provider credentials configured
The Terraform Workflow
Core Commands
terraform init
- Initialize working directoryterraform plan
- Preview changesterraform apply
- Create/update infrastructureterraform destroy
- Remove infrastructure
Setting Up Your First Project
Project Structure
my-first-infrastructure/
āāā main.tf # Main configuration
āāā variables.tf # Input variables (optional)
āāā outputs.tf # Output values (optional)
āāā terraform.tfvars # Variable values (optional)
Create Project Directory
mkdir my-first-infrastructure
cd my-first-infrastructure
Writing Your First Configuration
Basic Configuration Components
1. Provider Configuration
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-west-2"
}
2. Resource Definition
# Create an EC2 instance
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1d0" # Amazon Linux 2 AMI
instance_type = "t2.micro"
tags = {
Name = "terraform-example"
}
}
Complete First Configuration
Create main.tf
with the following content:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-west-2"
}
# Data source to get latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# Create a VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "terraform-vpc"
}
}
# Create Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "terraform-igw"
}
}
# Create a subnet
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = true
tags = {
Name = "terraform-subnet"
}
}
# Create route table
resource "aws_route_table" "main" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "terraform-rt"
}
}
# Associate route table with subnet
resource "aws_route_table_association" "main" {
subnet_id = aws_subnet.main.id
route_table_id = aws_route_table.main.id
}
# Create security group
resource "aws_security_group" "web" {
name = "terraform-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
# HTTP access
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# SSH access
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this in production!
}
# All outbound traffic
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-web-sg"
}
}
# Create EC2 instance
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
subnet_id = aws_subnet.main.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "terraform-web-server"
}
}
Understanding the Configuration
Resource Blocks
resource "RESOURCE_TYPE" "RESOURCE_NAME" {
# Resource arguments
argument1 = "value1"
argument2 = "value2"
}
Resource Dependencies
Terraform automatically creates dependencies based on resource references:
# This instance depends on the subnet and security group
resource "aws_instance" "web" {
subnet_id = aws_subnet.main.id # Dependency
vpc_security_group_ids = [aws_security_group.web.id] # Dependency
}
Data Sources
# Data sources fetch information from existing resources
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
# ... filters
}
Executing the Terraform Workflow
Step 1: Initialize
terraform init
What happens:
- Downloads AWS provider plugin
- Creates
.terraform
directory - Initializes backend (local by default)
Expected output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.0.1...
Terraform has been successfully initialized!
Step 2: Validate
terraform validate
What happens:
- Checks configuration syntax
- Validates resource arguments
- Ensures required arguments are present
Step 3: Format (Optional)
terraform fmt
What happens:
- Formats configuration files
- Ensures consistent style
- Safe to run repeatedly
Step 4: Plan
terraform plan
What happens:
- Shows what changes will be made
- Compares desired state vs current state
- No actual changes are made
Expected output:
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0c55b159cbfafe1d0"
+ instance_type = "t2.micro"
# ... more attributes
}
Plan: 6 to add, 0 to change, 0 to destroy.
Step 5: Apply
terraform apply
Interactive approval:
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
Auto-approve (use carefully):
terraform apply -auto-approve
Inspecting Your Infrastructure
Show Current State
terraform show
List Resources
terraform state list
Get Specific Resource Info
terraform state show aws_instance.web
AWS Console Verification
- Log into AWS Console
- Navigate to EC2 service
- Verify your instance is running
- Check VPC, subnet, security group creation
Adding Outputs
Create outputs.tf
:
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = aws_instance.web.public_ip
}
output "website_url" {
description = "URL to access the website"
value = "http://${aws_instance.web.public_ip}"
}
View Outputs
# After apply, view outputs
terraform output
# Get specific output
terraform output instance_public_ip
Modifying Infrastructure
Update Configuration
Modify the instance tags in main.tf
:
resource "aws_instance" "web" {
# ... existing configuration
tags = {
Name = "terraform-web-server"
Environment = "development"
Project = "terraform-tutorial"
}
}
Apply Changes
terraform plan
terraform apply
Terraform will show:
# aws_instance.web will be updated in-place
~ resource "aws_instance" "web" {
~ tags = {
+ "Environment" = "development"
+ "Project" = "terraform-tutorial"
"Name" = "terraform-web-server"
}
}
Testing Your Infrastructure
Access Your Web Server
# Get the public IP
terraform output instance_public_ip
# Test HTTP access
curl http://$(terraform output -raw instance_public_ip)
Expected response:
<h1>Hello from Terraform!</h1>
Cleaning Up
Destroy Infrastructure
terraform destroy
Warning prompt:
Terraform will destroy all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
What gets destroyed:
- EC2 instance
- Security group
- Subnet
- Route table and association
- Internet gateway
- VPC
Common Issues and Solutions
Authentication Errors
# Check AWS credentials
aws configure list
# Set credentials if needed
aws configure
AMI Not Found
Update the AMI ID or use data source:
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
# ...
}
Resource Already Exists
# Import existing resource
terraform import aws_instance.web i-1234567890abcdef0
Best Practices Introduced
1. Use Data Sources
Instead of hardcoding AMI IDs, use data sources to find the latest AMI.
2. Meaningful Names
Use descriptive names for resources that indicate their purpose.
3. Tags
Always tag your resources for better organization and cost tracking.
4. Security Groups
Create specific security groups rather than using default ones.
5. Comments
Add comments to explain complex configurations.
Key Takeaways
- Terraform uses declarative configuration to define infrastructure
- The workflow is: init ā plan ā apply ā destroy
- Resources have dependencies that Terraform manages automatically
- Always run
terraform plan
beforeterraform apply
- Use
terraform destroy
to clean up resources and avoid charges - Data sources help make configurations more dynamic and maintainable
Next Steps
- Complete Tutorial 4: Change Infrastructure
- Learn about variables and outputs
- Explore different resource types
- Practice with different cloud providers