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/terraformLinux (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 terraformWindows
choco install terraformVerify the installation:
terraform version
# Terraform v1.10.xHCL 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.hclThe Core Workflow
1. terraform init
Initializes the working directory. Downloads providers and modules.
terraform initInitializing 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 planTerraform 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:
| Symbol | Meaning |
|---|---|
+ | Create |
~ | Update in place |
- | Destroy |
-/+ | Destroy and recreate |
3. terraform apply
Executes the plan. Terraform asks for confirmation before proceeding.
terraform applyTo skip the confirmation prompt (useful in CI/CD):
terraform apply -auto-approve4. terraform destroy
Removes all resources managed by this configuration.
terraform destroyThis 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 nameUseful Commands
| Command | Purpose |
|---|---|
terraform fmt | Format your .tf files |
terraform validate | Check syntax without connecting to providers |
terraform show | Show current state |
terraform output | Display output values |
terraform state list | List all resources in state |
terraform refresh | Update 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
init→plan→apply→destroy - Always review the plan before applying changes
- Use
terraform fmtandterraform validateto keep your code clean