Learn Terraform in Minutes
Learn Terraform in Minutes
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
Sold to
[email protected]
Introduction to Terraform
What is Terraform?
Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp. It allows you to define
and provision infrastructure using a declarative configuration language called HashiCorp Configuration
Language (HCL).
Infrastructure as Code: Define infrastructure in code files that can be versioned, reused, and shared
Multi-cloud support: Works with AWS, Azure, Google Cloud, and many other providers
Declarative approach: You specify the desired state, and Terraform figures out how to achieve it
State management: Tracks the current state of your infrastructure
Dependency management: Automatically handles resource dependencies
Key Benefits
macOS
Windows
Linux
Verify Installation
terraform -v
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"] Sold to
[email protected]
}
Terraform Workflow
HCL (HashiCorp Configuration Language) is the language used to write Terraform configurations.
Blocks
Comments
# This is a comment
// This is also a comment
/* This is a
multi-line comment */
name = "server"
description = "This is a ${var.environment} server"
# Format code
terraform fmt Sold to
[email protected]
# 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
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
count
tags = {
Name = "server-${count.index}"
}
}
for_each
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = "server-${each.key}"
}
}
lifecycle
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [tags]
}
}
provider
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:
Sold to
terraform apply -var="region=us-east-1" -var="instance_type=t2.small" [email protected]
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
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
' 5%% 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" Sold to
public_subnets = [ [email protected]
"10.0.1.0/24",
"10.0.2.0/24"
]
tags = {
Environment = "Development"
Project = "Example"
}
}
tags = {
Name = "example-instance"
}
}
Module Sources
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 Composition
Workspaces allow you to manage multiple environments (like dev, staging, production) with the same
configuration files but separate state.
Managing Workspaces
# Select a workspace
terraform workspace select prod
# Delete a workspace
terraform workspace delete dev
tags = {
Sold to
Environment = terraform.workspace [email protected]
Name = "${terraform.workspace}-instance"
}
}
Provisioners
Types of Provisioners
local-exec
provisioner "local-exec" {
command = "echo ${self.private_ip} >> private_ips.txt"
}
}
remote-exec
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
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
provisioner "local-exec" {
when = create
command = "echo 'Instance created'"
}
}
on-destroy
provisioner "local-exec" {
when = destroy
command = "echo 'Instance destroyed'"
}
}
failure behavior
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
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
}
locals {
timestamp = timestamp() # "2023-01-01T12:34:56Z"
timeadd = timeadd(timestamp(), "1h") # Add 1 hour
formatdate = formatdate("YYYY-MM-DD", timestamp())
}
IP Network Functions
locals { Sold to
[email protected]
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"]
locals {
to_string = tostring(42) # "42"
to_number = tonumber("42") # 42
to_bool = tobool("true") # true
to_list = tolist(["a", "b"]) # ["a", "b"]
to_map = tomap({a = 1, b = 2}) # {a = 1, b = 2}
to_set = toset(["a", "b", "a"]) # ["a", "b"]
}
locals {
file_content = file("${path.module}/example.txt")
template = templatefile("${path.module}/template.tpl", {
name = "John"
items = ["apple", "banana"]
})
}
Loops, Conditionals, and Dynamic
Blocks
Count
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${count.index}"
}
}
For Each
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = "server-${each.key}"
}
}
Conditional Expressions
ebs_block_device {
volume_size = var.environment == "prod" ? 100 : 20
}
}}
Dynamic Blocks
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
Sold to
protocol = ingress.value.protocol [email protected]
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
}
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
}
Testing and Validation
Variable Validation
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
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:
Sold to
package test [email protected]
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
name: Terraform
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Terraform in GitLab CI
stages:
- validate
- plan
- apply
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
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"
}
}
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "terraformstate"
container_name = "terraform-state"
key = "terraform.tfstate"
}
}
terraform {
backend "gcs" {
bucket = "terraform-state-bucket"
prefix = "terraform/state"
}
} Sold to
[email protected]
terraform {
backend "remote" {
organization = "example-org
Advanced State
Management
Beyond basic state operations for complex Terraform deployments
State Locking
1
Prevents concurrent state modifications and conflicts
State Import
2
Bring existing infrastructure under Terraform management
State Migration
4
Move resources between state files or workspaces
Code Organization
1 Structure repos by environment and component. Use consistent naming conventions for all
resources.
Version Control
2
Pin provider versions and modules. Store state remotely with proper locking mechanisms.
Security First
3
Restrict IAM permissions. Never hardcode secrets. Use environment variables or secure vaults.
Validation Pipeline
4
Implement terraform fmt, validate, and plan in CI/CD. Add automated testing with Terratest.
Sold to
[email protected]
Terraform Troubleshooting
Initialization Issues 1
Run terraform init with -upgrade flag to
refresh providers and modules.
2 Plan and Apply Failures
Error: Failed to query available provider Use -target flag to isolate problematic
packages resources during troubleshooting.
Solution: terraform init -upgrade
Error: Error creating EC2 instance
Solution: terraform apply -
State Conflicts target=aws_vpc.main
3
Resolve state locks with force-unlock when
processes terminate unexpectedly.