What is Terraform?
Terraform is a tool that lets you create and manage cloud infrastructure by writing it down as code instead of clicking around in a web console. You describe what you want — a server, a network, a DNS record — in plain text files, and Terraform talks to your cloud provider’s API to make it real. It is the most popular provisioning tool in DevOps today, and learning it is one of the highest-value skills for a Linux and cloud engineer. This page is a friendly overview; the deep dive lives in the dedicated IaC docs.
What “provisioning” means
Provisioning means creating the actual infrastructure that your apps run on: virtual machines (cloud servers), networks, firewalls, load balancers, databases, and DNS records (the entries that map a domain name like example.com to a server’s IP address).
Before tools like Terraform, you provisioned infrastructure by hand — logging into the AWS, DigitalOcean, or Azure web console and clicking buttons. That works for one server, but it falls apart fast: it is slow, easy to get wrong, impossible to review, and nobody can remember exactly what was clicked six months ago. Infrastructure as Code (IaC) — describing infrastructure in text files you commit to Git — fixes all of that. Terraform is the leading IaC provisioning tool.
When to use Terraform: any time you create cloud resources that should be repeatable, reviewable, and version-controlled. If you are clicking the same buttons in a cloud console more than once, you want Terraform instead.
Declarative HCL: you describe the goal, not the steps
Terraform configuration is written in HCL (HashiCorp Configuration Language) — a simple, readable text format made for describing infrastructure.
HCL is declarative, which means you describe the end result you want, not the step-by-step instructions to get there. You say “I want one server of this size in this region,” and Terraform figures out whether it needs to create it, change it, or leave it alone. Compare that to an imperative script (like a bash script) where you spell out every command in order.
Here is a tiny example that creates one DigitalOcean server (called a “droplet”):
# main.tf
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
}
}
}
provider "digitalocean" {
token = var.do_token # your API token, passed in securely
}
resource "digitalocean_droplet" "web" {
name = "web-1"
image = "ubuntu-24-04-x64"
region = "nyc3"
size = "s-1vcpu-1gb"
}
You never told Terraform how to create the server. You only described the server you wanted. That is the power of declarative.
Providers: how Terraform talks to clouds
A provider is a plugin that teaches Terraform how to talk to a specific platform’s API. There is an AWS provider, an Azure provider, a Google Cloud provider, a DigitalOcean provider, a Cloudflare provider (for DNS), and hundreds more.
This is what makes Terraform so flexible: one tool, one language, but it can provision almost anything. You can manage AWS servers, Cloudflare DNS, and a Postgres database all from the same set of files.
State: how Terraform remembers what it built
This is the concept beginners miss most. Terraform keeps a state file (by default terraform.tfstate) that records everything it has created and maps it to your .tf files. State is how Terraform knows the difference between “create a new server” and “this server already exists.”
When you run terraform plan, Terraform compares three things: your .tf files (what you want), the state file (what it thinks exists), and the real cloud (what actually exists). Then it shows you exactly what it will add, change, or destroy before it touches anything.
Gotcha — never commit state to Git. The state file can contain secrets in plain text (passwords, tokens). Add
terraform.tfstate*and.terraform/to your.gitignore. For teams, store state remotely (for example in an S3 bucket with locking) so two people cannot run Terraform at the same time and corrupt it.
The core workflow
You drive Terraform with a handful of commands. Install it on Ubuntu first:
sudo apt update
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 terraform
terraform version
Output:
Terraform v1.9.5
on linux_amd64
Then the everyday loop runs inside your project folder:
terraform init # download the providers your config needs
terraform plan # preview the changes (creates nothing)
terraform apply # actually build the infrastructure
terraform destroy # tear it all back down
Output (from terraform plan):
Terraform will perform the following actions:
# digitalocean_droplet.web will be created
+ resource "digitalocean_droplet" "web" {
+ image = "ubuntu-24-04-x64"
+ name = "web-1"
+ region = "nyc3"
+ size = "s-1vcpu-1gb"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Always read the plan before you type terraform apply. That preview is your safety net.
Terraform vs Ansible: when to use which
Terraform and Ansible (a configuration-management tool) are often confused because both are “infrastructure as code.” But they solve different problems, and a real DevOps setup usually uses both.
| Question | Terraform | Ansible |
|---|---|---|
| Main job | Provision infrastructure (create servers, networks, DNS) | Configure what’s inside a server (install Nginx, copy configs, run updates) |
| Style | Declarative (describe the goal) | Mostly procedural (list of tasks to run in order) |
| Tracks state? | Yes, in a state file | No, it checks the live system each run |
| Best at | Standing up cloud resources | Software install + ongoing config |
| Typical use | ”Give me 3 Ubuntu servers and a load balancer" | "On those servers, install and start Nginx” |
The common pattern: Terraform builds the empty servers, then Ansible configures them. Use Terraform for creating the box, and Ansible for setting up the box.
Best practices
- Keep all
.tffiles in Git and review changes through pull requests, just like application code. - Always run
terraform planand read the output beforeterraform apply— never apply blind. - Store state remotely (S3 + DynamoDB locking, or Terraform Cloud) for any team project; never commit
terraform.tfstateto Git. - Pass secrets like API tokens through environment variables (
TF_VAR_do_token) or a secrets manager, not hardcoded in files. - Pin provider and Terraform versions so builds are reproducible across machines.
- Use Terraform for provisioning and a config tool like Ansible for in-server setup — pick the right tool for each job instead of forcing one to do both.