Terraform_ From Zero to Expert
Terraform_ From Zero to Expert
A Comprehensive Guide
Table of Contents
1. Introduction to Terraform
2. Getting Started
3. Core Concepts
4. Terraform Basics
5. Resource Management
6. Variables and Outputs
7. State Management
8. Modules
9. Terraform Workspaces
10.Provisioners
11.Functions
12.Loops, Conditionals, and Dynamic Blocks
13.Testing and Validation
14.CI/CD Integration
15.Remote Backends
16.Advanced State Management
17.Best Practices
18.Troubleshooting
19.Real-world Projects
20.Advanced Techniques
Introduction to Terraform
What is Terraform?
Key Benefits
Getting Started
Installation
macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Windows
choco install terraform
Linux
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release
-cs) main"
sudo apt-get update && sudo apt-get install terraform
Verify Installation
terraform -v
First Terraform Project
mkdir terraform-demo
cd terraform-demo
tags = {
Name = "terraform-example"
}
}
Initialize Terraform:
terraform init
terraform plan
terraform apply
terraform destroy
Core Concepts
Providers
Providers are plugins that allow Terraform to interact with various cloud providers, services, and
APIs.
provider "aws" {
region = "us-east-1"
}
provider "google" {
project = "my-project"
region = "us-central1"
}
Resources
Data Sources
Data sources allow Terraform to use information defined outside of Terraform or by another
Terraform configuration.
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
Terraform Workflow
Terraform Basics
HCL Syntax
Blocks
block_type "label" "name" {
key = value
}
Comments
# This is a comment
// This is also a comment
/* This is a
multi-line comment */
# Format code
terraform fmt
# Validate configuration
terraform validate
# Apply changes
terraform apply
# Destroy resources
terraform destroy
# Show state
terraform state list
terraform state show aws_instance.example
Resource Management
Basic Resource Creation
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
acl = "private"
tags = {
Environment = "Production"
Project = "Data Storage"
}
}
Resource Dependencies
# Explicit dependency
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
depends_on = [aws_s3_bucket.data]
}
# Implicit dependency
resource "aws_eip" "ip" {
instance = aws_instance.web.id
}
Resource Meta-Arguments
depends_on
resource "aws_instance" "example" {
# ...
depends_on = [aws_s3_bucket.example]
}
count
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${count.index}"
}
}
for_each
resource "aws_instance" "server" {
for_each = {
web = "t2.micro"
app = "t2.small"
db = "t2.medium"
}
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = "server-${each.key}"
}
}
lifecycle
resource "aws_instance" "example" {
# ...
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [tags]
}
}
provider
resource "aws_instance" "example" {
provider = aws.west
# ...
}
Variable Declaration
# variables.tf
variable "region" {
description = "AWS region to deploy resources"
type = string
default = "us-west-2"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "instance_count" {
description = "Number of instances to create"
type = number
default = 1
}
variable "enabled" {
description = "Whether to create the resources"
type = bool
default = true
}
variable "subnet_ids" {
description = "List of subnet IDs"
type = list(string)
}
variable "tags" {
description = "Tags for resources"
type = map(string)
default = {}
}
variable "instance_settings" {
description = "Map of EC2 instance settings"
type = object({
ami = string
instance_type = string
subnet_id = string
tags = map(string)
})
}
Using Variables
provider "aws" {
region = var.region
}
tags = merge(var.tags, {
Name = "example-${count.index}"
})
}
In a .tfvars file:
# terraform.tfvars
region = "us-east-1"
instance_type = "t2.small"
subnet_ids = ["subnet-12345", "subnet-67890"]
Command line:
Environment variables:
export TF_VAR_region=us-east-1
export TF_VAR_instance_type=t2.small
Local Values
locals {
common_tags = {
Project = "Example"
Environment = var.environment
Owner = "DevOps Team"
}
instance_name = "${var.environment}-instance"
}
Output Values
Outputs allow you to expose specific values that might be useful to the user:
# outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.example.id
}
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = aws_instance.example.public_ip
}
output "instance_details" {
description = "Map of instance details"
value = {
id = aws_instance.example.id
public_ip = aws_instance.example.public_ip
private_ip = aws_instance.example.private_ip
subnet_id = aws_instance.example.subnet_id
}
sensitive = false
}
State Management
Understanding Terraform State
Terraform state is a file that maps real-world resources to your configuration, tracks metadata,
and improves performance.
State Files
State Commands
# List resources in state
terraform state list
Modules
What are Modules?
Modules are containers for multiple resources that are used together, allowing code reuse and
organization.
Creating a Module
modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
tags = merge(var.tags, {
Name = var.name
})
}
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index}"
})
}
# modules/vpc/variables.tf
variable "name" {
description = "Name of the VPC"
type = string
}
variable "cidr_block" {
description = "CIDR block for the VPC"
type = string
}
variable "public_subnets" {
description = "List of public subnet CIDR blocks"
type = list(string)
default = []
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
Using Modules
module "vpc" {
source = "./modules/vpc"
name = "example-vpc"
cidr_block = "10.0.0.0/16"
public_subnets = [
"10.0.1.0/24",
"10.0.2.0/24"
]
tags = {
Environment = "Development"
Project = "Example"
}
}
tags = {
Name = "example-instance"
}
}
Module Sources
# Local path
module "vpc" {
source = "./modules/vpc"
}
# GitHub
module "vpc" {
source = "github.com/terraform-aws-modules/terraform-aws-vpc"
}
# Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
}
# Git repository
module "vpc" {
source = "git::https://example.com/vpc.git"
}
# S3 bucket
module "vpc" {
source = "s3::https://s3-eu-west-1.amazonaws.com/bucket-name/vpc.zip"
}
Module Composition
Terraform Workspaces
What are Workspaces?
Workspaces allow you to manage multiple environments (like dev, staging, production) with the
same configuration files but separate state.
Managing Workspaces
# Create a new workspace
terraform workspace new dev
# Select a workspace
terraform workspace select prod
# Delete a workspace
terraform workspace delete dev
tags = {
Environment = terraform.workspace
Name = "${terraform.workspace}-instance"
}
}
Provisioners
Types of Provisioners
local-exec
resource "aws_instance" "example" {
# ...
provisioner "local-exec" {
command = "echo ${self.private_ip} >> private_ips.txt"
}
}
remote-exec
resource "aws_instance" "example" {
# ...
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
}
}
}
file
resource "aws_instance" "example" {
# ...
provisioner "file" {
source = "conf/nginx.conf"
destination = "/tmp/nginx.conf"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
}
Provisioner Behaviors
on-create
resource "aws_instance" "example" {
# ...
provisioner "local-exec" {
when = create
command = "echo 'Instance created'"
}
}
on-destroy
resource "aws_instance" "example" {
# ...
provisioner "local-exec" {
when = destroy
command = "echo 'Instance destroyed'"
}
}
failure behavior
resource "aws_instance" "example" {
# ...
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
Functions
String Functions
locals {
upper = upper("hello") # "HELLO"
lower = lower("WORLD") # "world"
title = title("hello world") # "Hello World"
substr = substr("hello", 1, 3) # "ell"
join = join(",", ["a", "b"]) # "a,b"
split = split(",", "a,b") # ["a", "b"]
replace = replace("hello", "l", "L") # "heLLo"
trim = trim(" hello ") # "hello"
format = format("Hello, %s!", "World") # "Hello, World!"
}
Collection Functions
locals {
concat = concat(["a"], ["b"]) # ["a", "b"]
length = length([1, 2, 3]) # 3
element = element(["a", "b"], 1) # "b"
contains = contains(["a", "b"], "a") # true
keys = keys({a = 1, b = 2}) # ["a", "b"]
values = values({a = 1, b = 2}) # [1, 2]
lookup = lookup({a = 1, b = 2}, "a", 0) # 1
zipmap = zipmap(["a", "b"], [1, 2]) # {a = 1, b = 2}
merge = merge({a = 1}, {b = 2}) # {a = 1, b = 2}
}
Numeric Functions
locals {
abs = abs(-42) # 42
ceil = ceil(1.1) #2
floor = floor(1.9) #1
max = max(1, 2, 3) #3
min = min(1, 2, 3) #1
pow = pow(2, 3) #8
signum = signum(-42) # -1
}
IP Network Functions
locals {
cidr_subnets = cidrsubnets("10.0.0.0/16", 8, 8, 8)
# ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${count.index}"
}
}
For Each
resource "aws_instance" "server" {
for_each = {
web = "t2.micro"
app = "t2.small"
db = "t2.medium"
}
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = "server-${each.key}"
}
}
Conditional Expressions
resource "aws_instance" "server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.environment == "prod" ? "t2.medium" : "t2.micro"
ebs_block_device {
volume_size = var.environment == "prod" ? 100 : 20
}
}
Dynamic Blocks
resource "aws_security_group" "example" {
name = "example"
description = "Example security group"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
For Expressions
locals {
instance_ids = [for inst in aws_instance.server : inst.id]
instance_map = {
for inst in aws_instance.server :
inst.id => inst.public_ip
}
names = [for name, type in var.server_types : upper(name)]
filtered_instances = [
for inst in aws_instance.server :
inst.id
if inst.instance_type == "t2.micro"
]
}
Splat Expressions
locals {
instance_ids = aws_instance.server[*].id
}
validation {
condition = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
error_message = "The instance_type must be t2.micro, t2.small, or t2.medium."
}
}
lifecycle {
precondition {
condition = var.environment == "prod" ? var.instance_type == "t2.medium" : true
error_message = "Production environment must use t2.medium or larger instances."
}
}
}
output "instance_ip" {
value = aws_instance.example.public_ip
postcondition {
condition = length(aws_instance.example.public_ip) > 0
error_message = "The instance must have a public IP address."
}
}
Terratest is a Go library that makes it easier to write automated tests for your Terraform code:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
CI/CD Integration
Terraform in GitHub Actions
name: Terraform
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
image:
name: hashicorp/terraform:1.0.0
entrypoint: [""]
variables:
TF_ROOT: ${CI_PROJECT_DIR}
TF_STATE_NAME: default
before_script:
- cd ${TF_ROOT}
validate:
stage: validate
script:
- terraform init -backend=false
- terraform fmt -check -recursive
- terraform validate
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
stage: apply
script:
- terraform init
- terraform apply -auto-approve tfplan
dependencies:
- plan
only:
- main
Terraform in Jenkins Pipeline
pipeline {
agent any
tools {
terraform 'terraform-1.0.0'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Terraform Init') {
steps {
sh 'terraform init'
}
}
stage('Terraform Format') {
steps {
sh 'terraform fmt -check -recursive'
}
}
stage('Terraform Validate') {
steps {
sh 'terraform validate'
}
}
stage('Terraform Plan') {
steps {
sh 'terraform plan -out=tfplan'
}
}
stage('Approval') {
when {
branch 'main'
}
steps {
input message: 'Apply the terraform plan?'
}
}
stage('Terraform Apply') {
when {
branch 'main'
}
steps {
sh 'terraform apply -auto-approve tfplan'
}
}
}
}
Remote Backends
AWS S3 Backend
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}