Terraform Tutorial for Beginners: Infrastructure as Code From First File to Modules
Learn Terraform without a cloud account or a surprise bill. Start with local files to understand plan, apply and state, then move to real cloud resources, modules and a workflow that is safe for teams.
- Terraform creates and changes infrastructure from text files that you can keep in Git.
- The core workflow is
init,plan,apply, andplanshows changes before they happen. - The state file records what Terraform manages. Protect it, and never commit it to Git.
- Use modules to reuse patterns, and remote state with locking when working in a team.
Clicking around a cloud console is fine for one server. It breaks down when you need the same setup in staging and production, or when someone asks who changed a firewall rule last March. This Terraform tutorial for beginners shows you a better way: describe your infrastructure in code, and let a tool make it real.
Most tutorials begin with a cloud account, which adds billing risk before you have learned anything. We start with Terraform’s local provider, which just creates files on your computer, so mistakes cost nothing. These examples and the sample output are standard illustrations that we did not execute for this article, and exact wording varies by version.
What is Terraform?
Terraform is an infrastructure as code (IaC) tool from HashiCorp. You write configuration files in HCL, the HashiCorp Configuration Language, describing the end result you want. Terraform then works out what to create, change or delete in order to get there.
It talks to platforms through plugins called providers. There are providers for AWS, Azure, Google Cloud, Kubernetes, GitHub, Cloudflare and many others, so you can use one workflow across them all.
One thing to know: in 2023, HashiCorp moved Terraform to the Business Source License. The community then created OpenTofu, an open-source fork that uses very similar commands and configuration. What you learn here largely applies to both.
Why infrastructure as code?
| Question | Manual console work | Terraform |
|---|---|---|
| Who changed what? | Hard to find out | Git history |
| Can we copy staging to production? | Careful clicking | Reuse the code |
| What will this change do? | You find out afterwards | terraform plan shows you first |
| Can someone review it? | Not easily | A normal pull request |
| Disaster recovery | Depends on people’s memory | Recreate from code, plus data backups |
Terraform is also declarative. You state “there should be three servers”, not “create a server, three times”. If two exist already, it adds one. A script that simply repeats its steps could end up creating three more.
Install Terraform
Install it using the official HashiCorp instructions for your operating system, and then confirm that it works:
terraform -version
Create an empty folder for the project. Terraform reads every .tf file in the current folder, so one folder equals one configuration.
Your first configuration
Create a file named main.tf:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform\n"
}
Look at the resource block, as nearly everything you write follows this shape. local_file is the resource type, and greeting is your own name for it inside the configuration. Together, they form its address: local_file.greeting. The arguments inside describe what you want.
The workflow: init, plan, apply
terraform init
This prepares the folder and downloads the providers you asked for. Run it once at the start, and again whenever you add a provider or module. It also creates a .terraform.lock.hcl file, which records exact provider versions. Commit that file to Git.
terraform init
terraform plan
This compares your code with what exists and shows the planned changes, without touching anything:
terraform plan
# local_file.greeting will be created
+ resource "local_file" "greeting" {
+ content = "Hello from Terraform\n"
+ filename = "./hello.txt"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Learn the symbols, because you will read a lot of plans:
| Symbol | Meaning |
|---|---|
+ |
Create |
~ |
Update in place |
- |
Destroy |
-/+ |
Destroy and recreate |
terraform apply
This shows the plan again, asks for confirmation and makes the changes:
terraform apply
Type yes, and hello.txt appears. Now run terraform plan again. It should report no changes, because reality already matches your code. This property, where running it repeatedly causes no further change, is called idempotence.
Changing and destroying
Edit the content line and run terraform plan. Terraform spots the difference and proposes a change. For this resource, it replaces the file. Pay attention to replacements on real infrastructure, since recreating a database could mean losing data.
Next, delete hello.txt by hand and plan again. Terraform notices it is missing and offers to recreate it. A difference between your code and reality is called drift, and detecting it is one of the main benefits of the tool.
To remove everything this configuration manages, run:
terraform destroy
Variables and outputs
Hard-coded values stop you from reusing code. Input variables make a configuration flexible, and outputs report useful values back:
# variables.tf
variable "environment" {
type = string
description = "Deployment environment"
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Use dev, staging or prod."
}
}
# main.tf
resource "local_file" "config" {
filename = "${path.module}/${var.environment}.txt"
content = "environment=${var.environment}\n"
}
# outputs.tf
output "config_path" {
value = local_file.config.filename
}
Set values on the command line, or keep them in a file called terraform.tfvars, which is loaded automatically:
terraform apply -var="environment=staging"
Using local_file.config.filename in the output is also how resources refer to each other. Terraform reads those references to work out the correct order in which to create things.
Terraform state
After applying, you will find a file called terraform.tfstate. This is Terraform’s record of what it manages, mapping your code to real objects. Without it, Terraform would not know that local_file.greeting is that specific file.
Three rules matter here:
- Never commit state to Git. It can contain passwords and other secrets in plain text.
- Do not edit it by hand. Use commands such as
terraform state listandterraform state mv. - Use a remote backend for team projects. A shared backend stores state centrally and locks it, so two people cannot apply at once.
Here is a typical backend using Amazon S3, with a bucket you create in advance:
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "shop/prod/terraform.tfstate"
region = "ap-south-1"
encrypt = true
use_lockfile = true
}
}
Tip: Start every project with a .gitignore that excludes .terraform/, *.tfstate, *.tfstate.* and any *.tfvars files holding secrets.
A real cloud resource
The workflow does not change when you move to a cloud. Here is an S3 bucket on AWS. Check pricing first, and destroy anything you create while practising:
provider "aws" {
region = "ap-south-1"
}
resource "aws_s3_bucket" "reports" {
bucket = "my-unique-reports-bucket-2026"
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
Terraform gets credentials from your AWS CLI setup or environment variables. Never write access keys into a .tf file.
Modules
A module is simply a folder of Terraform files that you can call with different inputs. It works much like a function, letting you define a pattern once and reuse it:
project/
main.tf
modules/
reports_bucket/
main.tf
variables.tf
outputs.tf
module "staging_reports" {
source = "./modules/reports_bucket"
environment = "staging"
}
module "prod_reports" {
source = "./modules/reports_bucket"
environment = "prod"
}
The public Terraform Registry also has ready-made modules, but read them before you rely on them, and pin the version. Do not build modules too early. Wait until you have repeated a pattern at least twice.
A safe team workflow
- Create a branch and change the
.tffiles. See our Git tutorial for beginners if you need a refresher. - Run
terraform fmtandterraform validate. - Open a pull request, with the output of
terraform planattached for review. - After approval, apply that saved plan from one controlled place, ideally a pipeline.
Saving the plan ensures you apply exactly what was reviewed:
terraform plan -out=tfplan
terraform apply tfplan
This fits naturally into CI/CD and GitHub Actions. Terraform usually provisions the cluster, and tools like Kubernetes then run applications on top of it.
Common mistakes
- Applying without reading the plan. One
-/+on a database can be a very expensive surprise. - Keeping state on one laptop. Lose that laptop, and Terraform loses track of your infrastructure.
- Making manual console changes. They cause drift, and the next apply may undo them.
- Using unpinned versions. Pin provider and module versions so that upgrades happen on purpose.
- Using one huge configuration. Splitting by environment or system limits the damage of any mistake.
Final thoughts
You have now seen the ideas behind nearly all Terraform work: declare resources, review the plan, apply it, and protect the state. Practise with the local provider until the workflow feels natural, then manage one small real resource in a free-tier account. Bring in remote state and modules once more than one person, or more than one environment, is involved.
Frequently asked questions
What is Terraform used for?
Terraform is used to create and manage infrastructure, such as servers, networks, databases and DNS, by describing it in code instead of configuring it by hand.
Is Terraform free?
The Terraform CLI is free to use under its Business Source License, though terms restrict some competing commercial uses. HCP Terraform has paid tiers, and OpenTofu is open source.
What is the difference between Terraform and Ansible?
Terraform mainly provisions infrastructure declaratively. Ansible mainly configures software on existing machines. Many teams use both, Terraform first and Ansible afterwards.
What is a Terraform state file?
It is a record mapping your configuration to real resources. Terraform uses it to work out changes. It can hold secrets, so store it securely and keep it out of Git.
Do I need to know programming to learn Terraform?
Not really. HCL is simple and declarative. Understanding the platform you manage, such as AWS networking, is usually harder than the language itself.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.