Skip to main content

Terraform Basics

This lesson covers the foundational Terraform workflow that you'll use every day: writing configuration, previewing changes, and applying them.

Installing Terraform

macOS

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Linux (Ubuntu/Debian)

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

Windows

choco install terraform

Verify the installation:

terraform version
# Terraform v1.10.x

HCL Syntax

Terraform uses HashiCorp Configuration Language (HCL). It's designed to be human-readable while remaining machine-parsable.

Blocks

Everything in Terraform is a block:

block_type "label_1" "label_2" {
  argument = "value"

  nested_block {
    argument = "value"
  }
}

Common Block Types

# Provider  which cloud to talk to
provider "aws" {
  region = "us-east-1"
}

# Resource  something to create
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-app-assets-2026"
}

# Variable  input parameter
variable "environment" {
  type    = string
  default = "dev"
}

# Output  expose a value
output "bucket_url" {
  value = aws_s3_bucket.my_bucket.bucket_domain_name
}

# Data  read existing infrastructure
data "aws_ami" "ubuntu" {
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*-amd64-server-*"]
  }
}

# Locals  computed values
locals {
  name_prefix = "${var.environment}-myapp"
}

Types and Expressions

# Strings
name = "web-server"
name = "${var.prefix}-server"  # interpolation

# Numbers
count = 3

# Booleans
enabled = true

# Lists
subnets = ["subnet-abc", "subnet-def"]

# Maps
tags = {
  Name        = "web-server"
  Environment = "production"
}

# Conditionals
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"

Project Structure

A typical Terraform project:

my-infra/
├── main.tf          # Primary resources
├── variables.tf     # Input variables
├── outputs.tf       # Output values
├── providers.tf     # Provider configuration
├── terraform.tfvars # Variable values (don't commit secrets)
└── .gitignore       # Ignore .terraform/ and *.tfstate

Your .gitignore should include:

.terraform/
*.tfstate
*.tfstate.backup
*.tfvars       # if it contains secrets
.terraform.lock.hcl

The Core Workflow

1. terraform init

Initializes the working directory. Downloads providers and modules.

terraform init
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.82.0...

Terraform has been successfully initialized!

Run init when you:

  • Start a new project
  • Add or change providers
  • Add or change modules

2. terraform plan

Shows what Terraform will do without making changes.

terraform plan
Terraform will perform the following actions:

  # aws_s3_bucket.my_bucket will be created
  + resource "aws_s3_bucket" "my_bucket" {
      + bucket = "my-app-assets-2026"
      + id     = (known after apply)
      + arn    = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

The symbols mean:

SymbolMeaning
+Create
~Update in place
-Destroy
-/+Destroy and recreate

3. terraform apply

Executes the plan. Terraform asks for confirmation before proceeding.

terraform apply

To skip the confirmation prompt (useful in CI/CD):

terraform apply -auto-approve

4. terraform destroy

Removes all resources managed by this configuration.

terraform destroy

This is destructive. Always review the plan before confirming.

Your First Configuration

Create a file called main.tf:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.9.0"
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "website" {
  bucket = "my-static-site-2026"

  tags = {
    Name        = "Static Website"
    Environment = "dev"
  }
}

output "bucket_name" {
  value = aws_s3_bucket.website.bucket
}

Run it:

terraform init    # Download the AWS provider
terraform plan    # Preview the S3 bucket creation
terraform apply   # Create the bucket
terraform output  # See the bucket name

Useful Commands

CommandPurpose
terraform fmtFormat your .tf files
terraform validateCheck syntax without connecting to providers
terraform showShow current state
terraform outputDisplay output values
terraform state listList all resources in state
terraform refreshUpdate state to match real infrastructure

Summary

  • Install Terraform and verify with terraform version
  • HCL uses blocks, arguments, and expressions to describe infrastructure
  • The core workflow is initplanapplydestroy
  • Always review the plan before applying changes
  • Use terraform fmt and terraform validate to keep your code clean