I've started learning Terraform, and this is the first example from my TerraformExamples learning repo. It's the classic "hello world" of Infrastructure as Code: spin up a single AWS EC2 instance and learn the five core commands you'll use in pretty much every Terraform project after this.
Pre-requisites
Before running anything, get these out of the way first.
Install the Terraform CLI by following HashiCorp's install guide, then confirm it worked with terraform --version.
terraform --version
Install the AWS CLI as well, then run aws configure and provide your access key, secret key, and default region. This is what populates the credentials file that Terraform reads from later.
A couple of other things worth checking before you dive in:
- A default VPC should exist in your target AWS region. A fresh AWS account has one by default, so most people won't need to do anything here.
- Your AWS credentials need to be configured locally, at
%USERPROFILE%\.aws\credentialson Windows or$HOME/.aws/credentialson macOS/Linux. - The AMI ID used below needs to actually exist in your region. AMI IDs are region-specific and get deregistered over time, so if
terraform plancomplains about it, just swap in a current one for your region. Looking AMIs up dynamically through a data source is worth covering, but that's a topic for a later, more advanced example.
The Terraform manifest
Here's the entire configuration, just one file with three blocks.
# Terraform Settings Block
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Pin to a major version for reproducible plans
}
}
}
# Provider Block
provider "aws" {
profile = "default" # AWS Credentials Profile configured on your local machine
region = "us-east-1"
}
# Resource Block
resource "aws_instance" "ec2demo" {
ami = "ami-0b826bb6d96d2afe4" # Amazon Linux in us-east-1, update as per your region
instance_type = "t3.micro"
}
What each block does:
- The
terraformblock pins the CLI version and declares which providers this configuration needs. Pinningawsto~> 5.0means "any 5.x release," which keeps plans reproducible without locking you down to one exact patch version. - The
provider "aws"block tells Terraform which cloud account and region to talk to. Settingprofile = "default"just points it at the credentials profile already sitting in your local AWS config. - The
resourceblock is the actual infrastructure being requested.resource "aws_instance" "ec2demo"declares an EC2 instance, gives it a local name (ec2demo) that Terraform uses to track it in state, and sets the two required arguments,amiandinstance_type.
The five core commands
This example really exists to drill in the core Terraform workflow.
terraform init # download the provider plugin and set up the working directory
terraform validate # check the configuration is syntactically valid
terraform plan # preview what Terraform will create, change, or destroy
terraform apply # apply the plan and actually create the resources
initonly needs to run once per working directory, or whenever providers change, and it downloads thehashicorp/awsprovider binary into a local.terraformfolder.validateandplanare both safe, read-only checks that are worth running before everyapply.applyis the only command that actually touches infrastructure, and since it costs real money on AWS, it's the one worth double-checking before you confirm it.
Once apply finishes, head over to the AWS Console and check EC2 to confirm the instance is actually running.
Cleaning up
Since this is just a learning example, tear it down once you're done so it doesn't keep billing you.
terraform destroy
On Windows, also clean up the local Terraform working files (state and provider cache), but keep .terraform.lock.hcl committed.
Remove-Item -Recurse -Force .terraform
Remove-Item -Force terraform.tfstate*
The macOS/Linux equivalent is the same idea.
rm -rf .terraform
rm -rf terraform.tfstate*
It's worth keeping .terraform.lock.hcl and committing it to version control, since it locks the exact provider versions used, the same way package-lock.json does for npm.
Summary
Five commands cover the entire Terraform lifecycle. init sets up the working directory, validate and plan act as a safety net before touching anything real, apply provisions the infrastructure, and destroy tears it back down. Everything else in Terraform, whether that's variables, modules, remote state, or multiple environments, ultimately builds on top of this same loop.