Before we write Terraform code, we need to understand first where IaC was born: the evolution of infrastructure management from manual ClickOps, shell scripting, to declarative Infrastructure as Code — and why Terraform became the primary choice.

In episode 0, we already built the foundation: mastering basic Linux CLI skills, cloud computing concepts, structured data formats, as well as installing the Terraform CLI and preparing cloud credentials. Well, in this episode 1, we will step back from the keyboard and discuss why Terraform exists in this world — because before understanding how a tool works, we must first understand what problem it solves.
Why is understanding this history and concept important? Because automation tools pop up every year, and the decision to choose the right tool can't be made just because "this tool is popular." You have to understand what the declarative vs imperative paradigm is, what Infrastructure as Code (IaC) is, and why a tool ecosystem can become an industry standard for years. With this understanding, you won't just be able to use Terraform, but you'll also be able to argue technically about when to use Terraform, when to use Ansible, and when a shell script is enough.
In this episode we'll discuss three things: first, the evolution journey of IT infrastructure management from manual to IaC; second, the comparison of declarative, imperative, and code-based paradigms; third, the strong reasons why Terraform (and its open-source fork, OpenTofu) became the primary choice in the modern world.
To understand IaC, we need to know how humans managed servers before IaC existed. Imagine this journey as three eras, where each era was born as a solution to the pain of the previous era.
In the early cloud era, creating infrastructure meant opening the GUI Console in a browser (for example the AWS Management Console), then clicking menu after menu: click "Launch Instance", pick an AMI, pick an instance type, configure the security group, then click "Launch" — and repeating the same process for every server. This approach is popularly called ClickOps.
Imagine having to create 10 web servers, 3 databases, 2 load balancers, and 1 VPC by clicking. How many times would you have to switch tabs, wait for pages to load, and make sure not a single click is missed? That's just one environment. Now imagine having to create three identical environments (dev, staging, production) — a total of about 45 components clicked manually one by one.
Important
ClickOps' biggest problem isn't its speed, but its inconsistency. Two engineers asked to create "the same staging environment" will almost certainly produce two different infrastructures: one forgets to add a tag, the other uses a different security group, and so on. Infrastructure becomes like snow — no two are truly identical.
The problems with ClickOps can be summarized as follows:
As a solution, engineers began writing shell scripts to automate the provisioning process. Instead of clicking, they ran scripts that called the cloud CLI (aws ec2 run-instances, gcloud compute instances create, etc.).
#!/bin/bash
set -euo pipefail
for i in 1 2 3; do
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t3.micro \
--subnet-id subnet-12345678 \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=web-0$i}]"
done
aws ec2 create-tags \
--resources i-0abcd... \
--tags Key=Environment,Value=stagingThis approach is imperative: we write down step by step how to reach the end state. This script is clearly better than clicking — it can be repeated, it can be run in CI. But this era brought its own new problems:
if, for, and error handling logic that obscure the essence of "what we want to build".The key phrase here: imperative explains the how, not the what. This is the fundamental weakness that gave birth to the next era.
In the third era, Infrastructure as Code (IaC) was born — the radical idea that infrastructure should be defined as code describing the desired end state, not a sequence of steps to reach that state. Engineers write "this is what I want", and the tool figures out "how to make it happen".
resource "aws_instance" "web" {
for_each = toset(["01", "02", "03"])
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.app.id
tags = {
Name = "web-${each.key}"
Environment = "staging"
}
}
resource "aws_subnet" "app" {
cidr_block = "10.0.1.0/24"
vpc_id = aws_vpc.main.id
}Notice the difference. The bash script above says "do this, then this, then that". The Terraform code above says "I want 3 instances with these specs in this subnet". Terraform will compare this condition with the actual condition in the cloud, then determine the needed steps by itself. This is the essence of declarative: we declare the end result, the tool executes.
The IaC era brought major changes:
Note
The best analogy for declarative IaC is an architect's blueprint vs worker's work instructions. The blueprint states "this is the desired end result" — dimensions, materials, number of rooms — and the contractor (tool) translates it into work steps. Work instructions (scripts) instead list step by step without describing the end result, so two different workers can produce two different buildings from the same instructions.
Now, it's important to realize: IaC isn't a single approach. There are several paradigms coexisting in the real world, and each has its own strengths and weaknesses. Let's break down the three main paradigms:
As already discussed, this paradigm focuses on desired state. You write "what I want to have", the tool handles "how to get it". Terraform, OpenTofu, and AWS CloudFormation are examples.
The imperative paradigm in configuration management still writes execution steps, but organized in repeatable, structured modules/tasks. Ansible for example writes "install this package, copy this file, restart this service" — but because Ansible's modules are idempotent, in practice it's called intent-based. The fundamental difference with Terraform: Ansible typically works on servers that already exist (configuring inside the machine), while Terraform focuses on creating and managing infrastructure (network, storage, instances) that become the container for those servers.
The third paradigm uses general-purpose programming languages (TypeScript, Python, Go, C#) to define infrastructure. This gives full language power — loops, functions, classes, unit tests — but at a cost: you hold the control and responsibility for the logic yourself.
| Aspect | Declarative (Terraform/OpenTofu) | Imperative (Ansible/Puppet) | Code-based (Pulumi/CDK) |
|---|---|---|---|
| Main focus | Cloud infrastructure provisioning | In-server configuration | Provisioning & configuration |
| Writing style | Desired state (resource) | Sequential task/step (task) | Programming language (new Instance()) |
| Language | HCL (domain-specific) | YAML | TypeScript/Python/Go/C# |
| Learning curve | Low — focused on resources | Low — focused on tasks | High — requires understanding a programming language |
| Dependency resolution | Automatic (graph) | Manual & sequential | Manual/explicit |
| State tracking | Yes, terraform.tfstate | Not always / agent-based | Yes, in cloud service |
| Ecosystem & maturity | Very mature (2014–now) | Mature (Ansible 2012–now) | Newer, fast growing |
| When to use | Building & changing cloud infrastructure | Software configuration on servers | Teams wanting full language power |
Important
The classic "Terraform vs Ansible" question actually isn't a contest — the two are often used together in production. Terraform creates the VPC, subnets, instances, and load balancers (infrastructure). Ansible then goes into those instances to install and configure the software inside them (for example nginx or an application). Terraform "sets the stage", Ansible "arranges the props on the stage".
After understanding the evolution journey and paradigms, the big question is: out of the many IaC tools, why did Terraform become the de-facto standard? Here are the reasons.
This is Terraform's biggest differentiator since it was born in 2014 (written by HashiCorp, created by Mitchell Hashimoto). Before Terraform, each cloud had its own language: CloudFormation was AWS-only, Deployment Manager was GCP-only, ARM templates were Azure-only. Terraform brought one language (HCL) for all clouds.
There are more than thousands of official and community providers registered in the Terraform Registry: AWS, Google Cloud, Azure, Kubernetes, Docker, Cloudflare, GitHub, Datadog, down to niche providers like Linear or Railway. That means when a company uses multi-cloud or hybrid, the whole team only needs to learn one language to manage everything.
# AWS
resource "aws_instance" "aws_web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
# GCP
resource "google_compute_instance" "gcp_web" {
name = "gcp-web"
machine_type = "e2-micro"
zone = "asia-southeast1-a"
}
# Cloudflare (not a traditional cloud, but one language too!)
resource "cloudflare_record" "web" {
name = "www"
type = "A"
value = aws_instance.aws_web.public_ip
}Tip
This capability also makes Terraform skills portable between jobs. An engineer who has only ever used AWS can still contribute directly to a GCP team — the same HCL patterns, just swap the provider. This is a very real career selling point.
Terraform's strength isn't just the language, but also its ecosystem. Two things make it outstanding:
required_providers and Terraform downloads and manages the versions.module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"
name = "production-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-southeast-1a", "ap-southeast-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}Imagine production-grade VPC code (with NAT gateway, route tables, and Internet Gateway) that usually spans hundreds of lines — it can be replaced with a 15-line module call. This is the power of Don't Repeat Yourself (DRY) that we'll dive into in episode 9 later.
These two technical features are what make Terraform truly "know" your infrastructure's condition:
1. State File (terraform.tfstate). Terraform stores a map between HCL code and the real resources in the cloud in a state file. This is Terraform's "memory". When you run terraform plan, Terraform compares code vs state vs the actual condition in the cloud, then decides what needs to be created, changed, or deleted. Without state, Terraform wouldn't know that aws_instance.web actually already exists as i-0abcd1234 in AWS.
2. Dependency Graph. When you write code that references each other — for example an instance using a subnet, a subnet using a VPC — Terraform automatically builds a directed acyclic graph (DAG) of all resources and executes them in the correct order. Resources that don't depend on each other are executed in parallel for speed. This removes the manual burden of ordering that was a nightmare in shell scripting.
Warning
In episode 5 later we'll discuss state management in depth, including the dangers of lost, locked, or leaked local state files. For now, the important thing to remember: the state file is the most valuable asset of a Terraform project — it connects the world of code and the world of the cloud. Never delete it without a reason.
Terraform's story can't be separated from HashiCorp's decision in August 2023 to change Terraform's license from MPL 2.0 (open-source) to Business Source License (BSL) — a license that restricts commercial use. In response, the community formed OpenTofu under the Linux Foundation, a fork that continues Terraform under a fully open-source license (MPL 2.0).
Key points you should know:
tofu init, tofu plan, tofu apply, tofu destroy — the exact same patterns.Note
In short: Terraform is the product, HCL is the language, and OpenTofu is its open-source continuation. Whatever you choose, the conceptual foundation we build in this series applies to both.
In this episode 1 we've taken a long journey: understanding the evolution of infrastructure management from inconsistent manual ClickOps, to fragile imperative shell scripting, to the birth of reproducible and idempotent declarative Infrastructure as Code. We also compared the three IaC paradigms — declarative, imperative, and code-based — and understood Terraform's unique position among them.
Key takeaways you should bring along:
Now that the concepts are solid, it's time to start touching real code. In the next episode 2, we'll discuss HCL syntax anatomy and the Terraform core workflow — getting to know block structures, arguments, and identifiers, then practicing the four main workflow steps: terraform init, plan, apply, and destroy. Make sure your Terraform CLI is installed and ready, because in episode 2 we'll start writing our first configuration and running it! Stay excited, because from here the journey starts to enter the most fun part.