Table of Contents
What Terraform Is and When to Use It
Terraform is a declarative Infrastructure as Code (IaC) tool. You describe what infrastructure you want (servers, networks, databases, etc.) in configuration files, and Terraform figures out how to create, change, or destroy it.
Key characteristics:
- Declarative: you write the desired end state, not step‑by‑step instructions.
- Multi‑provider: works with many clouds and services (AWS, Azure, GCP, VMware, GitHub, Kubernetes, etc.).
- Stateful: keeps track of resources it created in a state file.
- Plan/apply cycle: shows proposed changes before applying them.
Typical use cases:
- Creating and updating cloud infrastructure (VPCs, VMs, load balancers, DBs).
- Managing DNS, CDNs, and security groups.
- Maintaining consistent environments (dev, staging, production).
- Version‑controlling infrastructure alongside application code.
Core Concepts
Configuration Files
Terraform configurations are plain text files (usually with .tf extension) written in HashiCorp Configuration Language (HCL).
A minimal layout:
main.tf– core resources and data sources.variables.tf– input variable definitions.outputs.tf– values to show afterapply.providers.tf– provider configuration (often merged intomain.tfin tiny projects).
Terraform reads all .tf files in a directory as a single configuration.
Providers
A provider is a plugin that knows how to talk to an API (e.g. AWS, Azure, GCP).
Basic provider block:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.5.0"
}
provider "aws" {
region = "us-east-1"
}Important points:
required_providersspecifies which providers and versions to use.provider "aws"configures how to connect (region, credentials, etc.).- Authentication details are typically taken from environment variables, profiles, or cloud CLIs rather than hard‑coding secrets.
Resources
A resource is a real piece of infrastructure Terraform manages, like a VM, database, or bucket.
Structure:
resource "<PROVIDER>_<TYPE>" "<NAME>" {
# arguments
}Example:
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
}"aws_instance"– resource type."web"– local name used for referencing inside configuration.- Terraform uses these definitions to create/update infrastructure.
Data Sources
A data source reads information from external systems without managing it.
Example:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}Data sources:
- Do not create resources.
- Are useful for lookup operations (e.g. latest AMI ID, existing VPC, secrets).
Inputs: Variables
Variables allow you to parameterize configurations.
Definition (usually in variables.tf):
variable "instance_type" {
description = "EC2 instance type for web servers"
type = string
default = "t3.micro"
}Usage:
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = var.instance_type
}Ways to set variable values:
terraform.tfvarsor*.auto.tfvarsfiles.-varand-var-fileCLI arguments.- Environment variables:
TF_VAR_instance_type="t3.small".
Outputs
Outputs expose values after apply finishes, useful for human consumption or for other tools.
Example:
output "web_public_ip" {
description = "Public IP of the web instance"
value = aws_instance.web.public_ip
}
After terraform apply, Terraform prints the output values.
Outputs are also important when combining modules (passing information between them).
Expressions and References
Terraform uses expressions to wire resources together.
Common patterns:
- Reference a resource attribute:
aws_instance.web.public_ip - Reference variable:
var.instance_type - String interpolation:
"web-${var.environment}" - Conditionals:
var.use_spot ? "spot" : "on-demand"
Example:
resource "aws_security_group" "web_sg" {
name = "web-sg"
}
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web_sg.id]
}
The reference aws_security_group.web_sg.id creates a dependency: Terraform ensures the security group exists before creating the instance.
Terraform CLI Workflow
Initialization: `terraform init`
terraform init:
- Downloads provider plugins and modules used in the configuration.
- Initializes the working directory.
Run it whenever:
- You create a new Terraform project.
- You add/change providers or modules.
- You change backend configuration (where state is stored).
Example:
$ terraform initPlanning Changes: `terraform plan`
terraform plan shows what Terraform will do without making changes.
It:
- Compares current configuration with the existing state and real infrastructure.
- Proposes actions:
+to add,-to destroy,~to change.
Example:
$ terraform planTypical uses:
- Review updates in pull requests.
- Validate that a change does what you expect.
- Identify drift (differences between code and real infrastructure).
Applying Changes: `terraform apply`
terraform apply:
- Creates, updates, or deletes resources to match the configuration.
- Prompts for confirmation unless you pass
-auto-approve.
Example:
$ terraform applyFor automation (CI/CD):
$ terraform apply -auto-approveDestroying Infrastructure: `terraform destroy`
terraform destroy removes all resources managed in the current state.
Example:
$ terraform destroyYou can selectively destroy resources with targeting (covered below), but be careful: deletions can be irreversible (data loss, downtime).
State: How Terraform Tracks Resources
Terraform uses a state file (default terraform.tfstate) to track which real resources correspond to which configuration blocks.
Key points:
- State maps resource addresses (like
aws_instance.web) to real IDs. - It stores metadata and attributes needed for planning.
- It is critical; losing it means Terraform no longer knows what it manages.
Local vs Remote State
By default, state is stored locally:
terraform.tfstatein the working directory.- Suitable for experiments or single‑user setups.
- Not suitable for team use (risk of conflicts and lost updates).
Remote state stores the state in a shared backend (e.g. S3, GCS, Azure Blob, Terraform Cloud), often with locking.
Example S3 backend:
terraform {
backend "s3" {
bucket = "my-tf-state-bucket"
key = "envs/prod/terraform.tfstate"
region = "us-east-1"
}
}For multi‑person or production usage:
- Use a remote backend.
- Use locking to prevent concurrent
applyoperations. - Protect state with encryption and restricted access (it often contains sensitive data).
State Locking and Concurrency
To avoid conflicts:
- Many backends support state locking.
- While
terraform applyorterraform planruns, the state is locked. - A second
applyin parallel will fail or wait.
Team practice:
- Run Terraform from CI/CD or a shared pipeline.
- Avoid running
applyconcurrently from laptops on the same state.
Basic Language Features
HCL Syntax
HCL basics:
- Blocks:
block_type "label1" "label2" { ... } - Arguments:
key = value - Lists:
[1, 2, 3] - Maps:
{ key1 = "value1", key2 = "value2" } - Comments:
#,//, or/ ... /
Example:
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = var.instance_type
tags = {
Name = "web-${var.environment}"
}
}Basic Types
Terraform has simple types:
stringnumberbool
And complex types (briefly):
list(<TYPE>)map(<TYPE>)object({ ... })tuple([ ... ])
You usually start with simple types, then use complex types as configurations grow.
Built‑in Functions (Overview)
Terraform provides standard library functions, such as:
- String:
upper(),lower(),replace(),format(). - Collections:
length(),concat(),merge(),keys(),values(). - Encoding:
jsonencode(),base64encode(). - Network:
cidrsubnet(),cidrhost().
Example:
tags = {
Name = upper("web-${var.environment}")
}Resource Graph and Dependencies
Terraform builds a dependency graph between resources based on references.
- If resource A references resource B, B must be created first.
- Terraform uses this graph to parallelize independent operations.
Example:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "web" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
Here, aws_subnet.web depends on aws_vpc.main because of vpc_id = aws_vpc.main.id.
You can also use explicit depends_on when references are not enough, but for fundamentals, implicit dependencies via references cover most cases.
Targeting and Lifecycle Basics
Resource Targeting
-target allows you to focus plan or apply on specific resources.
Example:
$ terraform apply -target=aws_instance.webUsage:
- For debugging or emergency operations.
- Not recommended as a long‑term workflow: it can create drift if used incorrectly.
Lifecycle Options (Overview)
The lifecycle block controls some aspects of how Terraform manages resources.
Example:
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
lifecycle {
prevent_destroy = true
}
}Common lifecycle attributes you will encounter:
prevent_destroy– stop accidental deletion.ignore_changes– ignore specific attribute changes.create_before_destroy– avoid downtime when replacing resources.
A Minimal End‑to‑End Example
This example shows a simple flow using a single provider and one resource.
main.tf:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.5.0"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
}
output "web_instance_id" {
value = aws_instance.web.id
}Workflow:
- Initialize:
$ terraform init- Preview:
$ terraform plan- Apply:
$ terraform apply- Destroy when done:
$ terraform destroy
This pipeline—init → plan → apply → (destroy)—is the foundation on which more advanced Terraform features (modules, workspaces, complex backends) are built and will be elaborated in subsequent chapters.