Skip to content

Terraform_basics

This chapter covers the fundamental building blocks of Terraform: providers, resources, and data sources.

Providers are plugins that Terraform uses to interact with cloud platforms, SaaS providers, and other APIs.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Terraform Providers │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Provider Block │ │
│ │ │ │
│ │ provider "aws" { │ │
│ │ region = "us-east-1" │ │
│ │ access_key = "..." # Or use AWS_ACCESS_KEY_ID env │ │
│ │ secret_key = "..." # Or use AWS_SECRET_ACCESS_KEY │ │
│ │ │ │
│ │ # Optional: Alias for multi-region configurations │ │
│ │ alias = "east" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Common Providers: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ AWS │ │ Azure │ │ GCP │ │Kubernetes│ │ Docker │ │
│ └─────────┘ └─────────┘ └─────────┘ └──────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# AWS Provider with assume role
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/AdminRole"
}
}
# Multiple AWS providers for different regions
provider "aws" {
region = "us-west-2"
alias = "west"
}
# Then reference with: provider = "aws.west"

Resources are the most important element in Terraform. They define the infrastructure components.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Resource Block Structure │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ resource "resource_type" "resource_name" { │
│ # Configuration block │
│ # │
│ # Required arguments │
│ # Optional arguments with defaults │
│ # │
│ # Arguments can reference: │
│ # - Variables │
│ # - Other resources │
│ # - Data sources │
│ } │
│ │
│ Example: │
│ resource "aws_instance" "web" { │
│ ami = "ami-0c55b159cbfafe1f0" │
│ instance_type = "t2.micro" │
│ tags = { Name = "WebServer" } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# EC2 Instance
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# Subnet placement
subnet_id = aws_subnet.main.id
# Security groups
vpc_security_group_ids = [aws_security_group.web.id]
# User data (bootstrap script)
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
EOF
tags = {
Name = "WebServer"
Environment = "Production"
}
}
# S3 Bucket
resource "aws_s3_bucket" "assets" {
bucket = "my-app-assets-${var.environment}"
tags = {
Name = "Assets Bucket"
Environment = var.environment
}
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "Main VPC"
}
}
# Security Group
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
# Ingress rules
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Egress rules
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "Web Security Group"
}
}

Data sources allow Terraform to read information from external sources without creating new resources.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Data Sources Concept │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Terraform │ │ External Data │ │
│ │ Config │────────▶│ Sources │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Resources │ │ Use in your │ │
│ │ to Create │ │ config │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ Common Data Sources: │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ aws_ami │ │ aws_vpc │ │ aws_subnet │ │
│ │ (Find AMIs) │ │ (Get VPC info) │ │ (Get Subnets) │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# 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"]
}
}
# Use the data source
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
tags = {
Name = "AppServer"
}
}
# Get existing VPC information
data "aws_vpc" "existing" {
default = true # Or use tags to filter
}
# Get subnet information
data "aws_subnet_ids" "public" {
vpc_id = data.aws_vpc.existing.id
tags = {
Type = "Public"
}
}
# Get account information
data "aws_caller_identity" "current" {}
output "account_id" {
value = data.aws_caller_identity.current.account_id
}
# Get AWS regions
data "aws_regions" "available" {}
output "all_regions" {
value = data.aws_regions.available.names
}

Resources can have explicit and implicit dependencies.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Resource Dependencies │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Implicit Dependency (Automatic): │
│ ┌──────────────┐ │
│ │ aws_vpc.main │──────────┐ │
│ └──────────────┘ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │aws_subnet.pub│ (Referenced by aws_vpc.main.id) │
│ └──────────────┘ │
│ │
│ Explicit Dependency (manual): │
│ ┌──────────────┐ │
│ │ aws_instance │──────────┐ │
│ └──────────────┘ │ │
│ ▼ │
│ ┌──────────────┐ depends_on = [ │
│ │ aws_iam_role │ aws_s3_bucket.assets │
│ └──────────────┘ ] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# Implicit dependency - Terraform figures it out automatically
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # This creates implicit dependency
}
# Explicit dependency - use when dependency is not in resource config
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# This instance must be created after the DB is ready
# (but doesn't directly reference db instance)
depends_on = [aws_db_instance.mysql]
}
# Lifecycle management
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
lifecycle {
# Create before destroy
create_before_destroy = true
# Prevent destroy
prevent_destroy = false
# Ignore changes to certain attributes
ignore_changes = [tags]
# Replace instance when AMI changes
replace_triggered_by = [data.aws_ami.amazon_linux.id]
}
}

Terraform provides several meta-arguments that work with any resource type.

# count - Create multiple instances
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "Server-${count.index}"
}
}
# for_each - Create instances from a map or set
resource "aws_instance" "servers" {
for_each = {
web = "t2.micro"
api = "t2.small"
db = "t2.medium"
}
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = each.key
}
}
# for_each with set
resource "aws_ebs_volume" "example" {
for_each = toset(["data", "logs", "backup"])
size = 100
availability_zone = "us-east-1a"
tags = {
Name = "${each.value}-volume"
}
}

Provisioners are used to execute scripts on local or remote machines after resource creation.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Provisioners Overview │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Types: │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ local-exec │ │ remote-exec │ │
│ │ (local machine)│ │ (remote target)│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ When to use: │
│ ✓ Initial setup scripts │
│ ✓ Configuration management │
│ ✓ Software installation │
│ │
│ Better alternatives: │
│ ✗ Use cloud-init for EC2 │
│ ✗ Use startup scripts for Azure/GCP │
│ ✗ Use configuration management tools (Ansible) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# Local provisioner - runs on machine running Terraform
resource "null_resource" "example" {
# Trigger on every change
triggers = {
always = timestamp()
}
provisioner "local-exec" {
command = "echo 'Instance ${aws_instance.web.id} created' >> /tmp/instances.txt"
}
}
# Remote provisioner - runs on created resource
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "remote-exec" {
inline = [
"sudo yum install -y nginx",
"sudo systemctl start nginx",
"sudo systemctl enable nginx",
]
}
}
# file provisioner - copy files to remote
provisioner "file" {
source = "scripts/init.sh"
destination = "/tmp/init.sh"
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}

In this chapter, you learned:

  • Providers: How to configure AWS, Azure, GCP, and other providers
  • Resources: Creating EC2, S3, VPC, Security Groups
  • Data Sources: Reading external information (AMIs, VPCs, accounts)
  • Dependencies: Implicit and explicit resource dependencies
  • Meta-arguments: Using count and for_each for multiple resources
  • Provisioners: Running local and remote scripts (and when to avoid them)