Quick answer: To write Terraform code you install the CLI, create a folder with a main.tf file, declare a provider (such as AWS), describe the resources you want in resource blocks, and run terraform init, plan and apply. Within fifteen minutes you can have a real server running from a 20-line text file.
This beginner’s guide takes you from an empty directory to a working EC2 instance, explains what every line does, then shows how to make the code reusable with variables and outputs. Along the way you will learn the file layout, commands and habits that professional DevOps engineers use every day.
Step 1: Install Terraform and set up credentials
Terraform is a single binary. On macOS use brew install hashicorp/tap/terraform; on Ubuntu add the HashiCorp apt repository and run sudo apt install terraform; on Windows use winget install HashiCorp.Terraform or Chocolatey. Verify with terraform -version — you want 1.5 or newer.
Next, give Terraform a way to authenticate to AWS. The simplest route is the AWS CLI: run aws configure and enter an access key for an IAM user with limited permissions. Terraform reads the same ~/.aws/credentials file automatically. Never paste keys into .tf files.
Step 2: Create the project folder and first file
Terraform treats a directory as one unit of configuration and reads every .tf file inside it. Create a folder called first-server and a file named main.tf:
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-south-1" # Mumbai
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "tkh-first-server"
}
}
Reading it top to bottom:
- The
terraformblock pins the Terraform version and declares that this project needs the AWS provider at any 5.x release. - The
providerblock configures where resources go.ap-south-1is Mumbai; choose the region closest to your users. - The
datablock asks AWS for the newest Ubuntu 24.04 image instead of hard-coding an AMI ID that differs between regions and goes stale. - The
resourceblock is the server itself.aws_instanceis the type;webis the name you will use to refer to it elsewhere.
Step 3: Run the core workflow
Three commands make up the Terraform workflow, and you will run them hundreds of times in your career.
# 1. Download providers, create .terraform/ and the lock file
terraform init
# 2. Preview what will change — nothing is created yet
terraform plan
# 3. Create the resources (type "yes" when prompted)
terraform apply
init runs once per project (and again whenever you add a provider or module). plan prints a diff showing + aws_instance.web and the values Terraform knows in advance; values such as the public IP show as (known after apply). apply executes the plan and writes terraform.tfstate, Terraform’s record of what it manages.
When you are done experimenting, run terraform destroy. It deletes everything in the state so you are not billed for a forgotten instance. The free tier covers roughly 750 hours of t3.micro or t2.micro per month for new accounts, but always clean up.
Step 4: Make the code reusable with variables and outputs
Hard-coded values work for a demo but not for a team that needs dev and prod. Move the changeable parts into variables and expose useful results as outputs. Create variables.tf:
variable "region" {
type = string
description = "AWS region for all resources"
default = "ap-south-1"
}
variable "instance_type" {
type = string
description = "EC2 size"
default = "t3.micro"
}
variable "name" {
type = string
description = "Name tag applied to the server"
}
Then outputs.tf:
output "public_ip" {
description = "Public IP of the web server"
value = aws_instance.web.public_ip
}
output "instance_id" {
value = aws_instance.web.id
}
Update main.tf to use region = var.region, instance_type = var.instance_type and Name = var.name. Because name has no default, Terraform will prompt for it — or you can supply it with terraform apply -var="name=tkh-dev" or a terraform.tfvars file. After apply, terraform output public_ip prints the address you can SSH into. For a deeper look at types, locals and built-in functions, read Using Variables and Expressions in Terraform.
Recommended file layout
| File | Purpose | Commit to Git? |
|---|---|---|
main.tf |
Providers and resources | Yes |
variables.tf |
Input variable declarations | Yes |
outputs.tf |
Values to display or pass on | Yes |
terraform.tfvars |
Variable values for this environment | Only if it holds no secrets |
.terraform.lock.hcl |
Exact provider versions | Yes |
.terraform/ |
Downloaded plugins | No |
terraform.tfstate |
Current state (may contain secrets) | No – use a remote backend |
The split is a convention, not a rule. Terraform merges all .tf files in the folder, so the layout exists purely to help humans find things.
Habits that separate beginners from professionals
- Format and validate before every commit.
terraform fmtfixes indentation;terraform validatecatches unknown arguments and type errors without touching the cloud. - Read the plan line by line.
-/+means destroy and recreate — fine for a stateless server, catastrophic for a database. - Use a remote backend early. Even solo, storing state in S3 with versioning protects you from a lost laptop.
- Tag everything. A
default_tagsblock in the provider addsManagedBy = "terraform"to every resource so nobody edits them by hand. - Add a
.gitignorefor.terraform/,*.tfstate,*.tfstate.backupand*.tfvarsfiles containing secrets.
Once your single server works, the next natural step is grouping resources into a module and understanding how Terraform orders their creation; see Creating and Managing Infrastructure Resources in Terraform.
Beginner mistakes to avoid
- Running
applyfrom the wrong folder. Terraform only sees the current directory, so you get “no configuration files” or, worse, apply the wrong project. - Copying an AMI ID from a tutorial. AMI IDs are region-specific; an
us-west-2ID fails in Mumbai. Use theaws_amidata source. - Forgetting
terraform initafter adding a provider or module. The error “provider not installed” almost always means this. - Editing resources in the AWS console. Terraform will detect the drift on the next plan and revert your change. Change the code, not the console.
- Leaving resources running. Always
destroypractice environments. Set a billing alarm on day one.
Frequently asked questions
Can I write Terraform code without an AWS account?
Yes. Use the local, random or null providers to practise syntax, or point the AWS provider at LocalStack, which emulates AWS services on your laptop.
Which editor should I use?
VS Code with the official HashiCorp Terraform extension gives you syntax highlighting, autocomplete for resource arguments, and inline documentation.
What does terraform init actually download?
The provider binaries listed in required_providers (the AWS provider is around 400 MB uncompressed) and any modules referenced by source. They land in the .terraform/ folder.
Is HCL hard to learn?
No. Most people are productive within a day. The challenge is not the language but understanding the cloud resources you are describing — subnets, security groups, IAM roles.
Key takeaways
- A Terraform project is a folder of
.tffiles: provider, resources, variables, outputs. - The workflow is always
init,plan,apply— anddestroywhen finished. - Use data sources instead of hard-coded IDs, and variables instead of repeated literals.
- Keep state and secrets out of Git; commit the lock file.
Ready to go beyond a single server and build real AWS and Azure environments with Terraform? Our DevOps course covers Terraform end to end with live projects, mentor support and placement assistance. Prefer video? Follow along on our YouTube channel.



