Terraform is a must-have skill for anyone working with infrastructure as code or modern cloud and DevOps workflows. This comprehensive beginner‘s guide will walk you through the fundamentals of getting started with Terraform.
What is Terraform?
Terraform is an infrastructure as code (IaC) tool developed by HashiCorp that allows you to define, provision and manage infrastructure across public cloud platforms like AWS, Azure, GCP and more in an easy-to-understand templated language.
In contrast to directly configuring cloud resources in portals like the AWS console, Terraform code serves as the single source of truth for your infrastructure and is version controlled alongside your application code. This helps codify and automate provisioning of resources consistently across environments.
Some key benefits of infrastructure managed as Terraform code include:
-
Infrastructure Documentation – Terraform code serves as living documentation of your cloud environment. Simply reviewing the code shows what infrastructure exists and how it relates.
-
Automation – Terraform allows codifying manual processes like spinning up new instances, improving efficiency and reducing human errors from repetitive tasks.
-
Version Control – Storing IaC configs in version control like git enables change tracking and collaboration around infrastructure changes.
-
Multi-cloud Portability – Terraform providers allow managing infrastructure across AWS, Azure, GCP and more from a common interface and codebase.
Overall Terraform brings cloud infrastructure under version control to enable consistent, collaborative provisioning and management as code in line with core DevOps principles. Next let‘s look at some core concepts in Terraform.
Core Terraform Concepts
Here are some key concepts and components that make up Terraform ecosystem:
Providers – Plugins responsible for managing resources from a particular infrastructure platform. For example the AWS provider handles interacting with the AWS APIs to create EC2 instances, databases and other supported resource types.
Resources – The most important concept in Terraform. Resources represent infrastructure objects like compute instances, storage, networks that you want to manage. Resources are defined in Terraform config files to provision them.
Variables – Input parameters for Terraform code in the form of key-value pairs. Variables allow customization of configs based on different environments.
Outputs – Read-only values exported after Terraform apply to indicate attributes of provisioned resources that can be accessed by other terraform configs or data sources.
Backend – Where Terraform state snapshots are stored remotely. This allows Terraform state to be shared for team collaboration.
Modules – Self-contained packages of Terraform configurations that are managed together and can be reused. Modules allow for logical abstraction and encapsulation.
State – Terraform state is a snapshot of the infrastructure and metadata managed by the configs. It maps real world resources to your configs and is used to determine diffs for changes.
Now that we have an understanding of Terraform basics, let‘s go over the terraform workflow…
Terraform Workflow
The Terraform lifecycle consists for the most part of the following key commands:
terraform init – Initializes the working directory and installs providers defined in the configs
terraform plan – Creates an execution plan to reach desired state defined in configs (what infrastructure should exist)
terraform apply – Provisions the infrastructure as outlined in the execution plan
terraform destroy – Destroys all provisioned infrastructure
This is the usual develop -> test -> deploy -> destroy cycle around making changes to infrastructure as code with Terraform.

Now let‘s look under the hood to see how Terraform works its magic…
How Terraform Works
At a high level Terraform is composed of the Terraform binary itself and providers for interacting with different infrastructure platforms like AWS, Azure etc.

The Terraform binary is responsible for a few key things:
- Taking as input your Terraform configs that specify desired infrastructure state
- Managing and persisting state mappings of real world resources
- Determining diffs between current vs desired infrastructure state
- Calling providers to create/update/delete infrastructure
Providers expose Terraform resources for managing infrastructure on their target platforms. Examples are the AWS provider for EC2 instances, VPCs and 100s more AWS resource types or the Kubernetes provider for namespaces, deployments and anything Kubernetes exposes.
This abstraction enables Terraform to remain a simple, portable interface for codifying infrastructure across providers. You don‘t have to learn intricacies of each underlying platform – just declare desired state in easy to understand configs and Terraform handles the rest!
Next let‘s walk through a hands-on Terraform demo…
Provisioning AWS infrastructure
Let‘s get our hands dirty with Terraform by using it to provision a few types of infrastructure on AWS including:
- VPC
- Public / private subnets
- EC2 instances + security groups
- Elastic load balancer
Here is a diagram of the infrastructure we‘ll be deploying:

Our goal is to provision a simple scalable 2-tier web architecture on AWS using Terraform.
1. Install Terraform
First, let‘s install Terraform on our machine using this one-liner:
curl -Ls https://raw.githubusercontent.com/terraform-linters/tflint/master/install_terraform.sh | bash
Now verify the install:
terraform -v
# Terraform v1.0.7
Great, we have the latest Terraform version installed and ready to go!
2. AWS Credentials
Next we need to configure AWS credentials for Terraform to access our account:
~/.aws/credentials
[my-aws-profile]
aws_access_key_id = <ACCESS_KEY>
aws_secret_access_key = <SECRET_KEY>
Set this profile as the default:
~/.aws/config
[default]
region = us-east-1
output = json
Validate we can access AWS with these credentials by listing S3 buckets:
aws s3 ls
# (list of s3 buckets)
Perfect! We‘re authenticated with AWS.
3. Terraform Configs
Now we‘re ready to define our AWS infrastructure as Terraform configs.
First create a Terraform module called aws-web-infra to encapsulate our configs:
mkdir aws-web-infra
cd aws-web-infra
main.tf
Let‘s start by defining the AWS provider and any input variables:
# Configure AWS provider
provider "aws" {
region = "us-east-1"
}
# Input variables
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
Next we can begin adding resource blocks for each component, beginning with the VPC:
vpc.tf
# Create VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "WebInfraVPC"
}
}
# Internet gateway for public subnet
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
# Public subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "WebPublicSubnet"
}
}
# Private subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "WebPrivateSubnet"
}
}
Next, the security groups, EC2 instances and load balancer:
compute.tf
# Web security group
resource "aws_security_group" "web" {
name = "AllowWebTraffic"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Bastion security group
resource "aws_security_group" "bastion" {
name = "AllowSSH"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Web auto scaling group
resource "aws_launch_configuration" "web" {
// AMI ID
instance_type = "t3.micro"
security_groups = [aws_security_group.web.id]
}
resource "aws_autoscaling_group" "web" {
availability_zones = ["us-east-1a"]
desired_capacity = 2
max_size = 4
min_size = 2
launch_configuration = aws_launch_configuration.web.name
vpc_zone_identifier = [aws_subnet.public.id]
}
# Bastion host instance
resource "aws_instance" "bastion" {
ami = "ami-022b9b2d9383732dc"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.bastion.id]
tags = {
Name = "BastionHost"
}
}
# Load balancer
resource "aws_lb" "web" {
internal = false
subnets = [aws_subnet.public.id]
}
resource "aws_lb_target_group" "web" {
port = 80
protocol = "HTTP"
target_type = "instance"
vpc_id = aws_vpc.main.id
}
resource "aws_autoscaling_attachment" "web" {
autoscaling_group_name = aws_autoscaling_group.web.name
alb_target_group_arn = aws_lb_target_group.web.arn
}
And finally any resource outputs:
outputs.tf
# Public IP of bastion host
output "bastion_public_ip" {
value = aws_instance.bastion.public_ip
}
# DNS of load balancer
output "lb_dns" {
value = aws_lb.web.dns_name
}
Alright our Terraform configs are ready to provision AWS infrastructure!
.
????????? compute.tf
????????? main.tf
????????? outputs.tf
????????? vpc.tf
Let‘s run through a Terraform workflow to provision this…
4. Terraform Initialization
First we initialize Terraform to install providers defined in the config:
terraform init
Initializing modules...
Downloading terraform-aws-modules/vpc/aws 2.21.0 for vpc...
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.74.0...
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
This will install the AWS provider we specified and various modules used in configs like the VPC.
5. Terraform Plan
Next, we‘ll create an execution plan to preview changes required to reach desired state:
terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_autoscaling_group.web will be created
+ resource "aws_autoscaling_group" "web" {
+ arn = (known after apply)
...
Plan: 11 to add, 0 to change, 0 to destroy.
The plan shows 11 resources that will be created including VPC, subnets, instances etc. This allows us to verify the changes before applying.
Once the plan looks good, we can move on to actually provisioning everything.
6. Terraform Apply
Run terraform apply and review the plan again. Type yes to confirm and provision:
terraform apply
# Review plan output here...
Do you want to perform these actions?
Terraform will perform the actions described above.
Only ‘yes‘ will be accepted to approve.
Enter a value: yes
# Apply output here...
It may take a few minutes to create all resources on AWS. Once done we should see them created!
Check your AWS console and you should now see a VPC, public/private subnets, security groups, an autoscaling group of web servers, bastion host and load balancer provisioned:

Hooray! We‘ve used Terraform to codify and provision our AWS web architecture.
To access the instances, we can SSH into the bastion host using its public IP output from Terraform, and from there connect to private instances.
The load balancer DNS name allows accessing the auto scaled webservers on port 80.
Now if we ever need to reproduce our setup such as in another AWS account or region, we can simply re-run terraform apply from the same config to quickly spin up a copy of the environment!
Some next steps to productionize this setup:
- Configuring remote Terraform state storage in S3 for team access
- Breaking configs out into modules for better organization
- Adding automated tests to validate Terraform changes
- Setting up CI/CD pipelines to apply terraform changes
But our main goal was to demonstrate provisioning real infrastructure as code with Terraform – and we‘ve succeeded!
Wrapping Up
We‘ve walked through Terraform fundamentals from top-level concepts to actually provisioning scalable architectures on AWS.
As you‘ve seen, Terraform allows codifying manual infrastructure processes to instead version, test and automate changes as code using simple declarative configs.
Some scenarios where Terraform shines:
- Onboarding new engineers by storing infra as code
- Quickly spinning up disposable environments for testing
- Promoting immutable infrastructure changes across envs
- Enforcing security policies and organizational standards
- And much more!
I hope you‘ve found this comprehensive guide useful as an introduction to Terraform basics. Infrastructure as code is critical for operating cloud and DevOps environments efficiently.
Give Terraform a try on your own infrastructure to see firsthand how defining everything as configs can simplify management. The Terraform documentation also expands on many additional features in more depth.
Let me know in the comments if you have any other Terraform topics you‘d like to see covered!