Terraform vs Ansible vs Helm
- lesson
- iac-tool-selection
- declarative-vs-imperative
- state-management
- operational-boundaries ---# Terraform vs Ansible vs Helm — When to Use Which
Topics: IaC tool selection, declarative vs imperative, state management, operational boundaries Level: L1–L2 (Foundations → Operations) Time: 45–60 minutes Prerequisites: None (each tool explained from scratch)
The Mission¶
You open a repo and find Terraform, Ansible, AND Helm in the same infrastructure/
directory. Three tools that all "manage infrastructure." Your PR adds an nginx config change,
and you have no idea which tool to use. Your coworker says "just put it in Ansible." Another
says "that should be a Helm value." A third says "we manage that in Terraform."
This lesson draws the boundary lines: what each tool is actually designed for, where they overlap, and how to decide which one to use for each task.
The 30-Second Version¶
| Tool | What it manages | How it works | State |
|---|---|---|---|
| Terraform | Cloud infrastructure (VPCs, instances, databases, DNS) | Declarative: "I want 3 servers" → Terraform figures out how | Explicit state file (critical) |
| Ansible | Server configuration (packages, files, services, users) | Procedural: "Run these tasks in order on these servers" | Stateless (checks current state each run) |
| Helm | Kubernetes workloads (Deployments, Services, ConfigMaps) | Declarative: "I want this app running in K8s" → Helm templates YAML | Release history in K8s secrets |
The simple rule: Terraform builds the house. Ansible furnishes it. Helm runs the apps.
Name Origins: Terraform = "transform the earth" (creating infrastructure from nothing). Ansible = a fictional device from Ursula K. Le Guin's novels that communicates instantly across any distance (fitting for a tool that configures remote servers). Helm = the ship's wheel that steers Kubernetes (Greek for "helmsman"). All three names reflect what they do.
Terraform: Creates the VPC, subnets, EC2 instances, RDS database, S3 buckets
↓
Ansible: Configures the EC2 instances (packages, users, sshd, monitoring)
↓
Helm: Deploys applications to the Kubernetes cluster
Where Terraform Fits¶
Terraform manages cloud resources — things you create through an API (AWS, GCP, Azure).
# Terraform: create a VPC, subnet, and EC2 instance
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
}
Use Terraform when: You need to create, modify, or destroy cloud resources. VPCs, security groups, load balancers, databases, DNS records, IAM roles — anything you'd otherwise click through in the AWS console.
Don't use Terraform when: You need to configure what's inside a server (packages,
files, services). Terraform can do it (with user_data or provisioners), but it's
awkward — Terraform's strength is API-driven resources, not SSH-based configuration.
Where Ansible Fits¶
Ansible manages server configuration — things you do over SSH.
# Ansible: configure the server Terraform created
- name: Configure web server
hosts: web_servers
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Copy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: restart nginx
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: true
handlers:
- name: restart nginx
service: name=nginx state=restarted
Use Ansible when: You need to install packages, manage config files, create users, configure firewalls, or run scripts on servers. Anything you'd do after SSH-ing in.
Don't use Ansible when: You need to create cloud resources. Ansible can do it
(with cloud modules), but it's stateless — it can't track what it created the way Terraform
does. Running ansible-playbook again might create duplicates.
Where Helm Fits¶
Helm manages Kubernetes applications — Deployments, Services, ConfigMaps, Ingress.
# Helm values.yaml: configure the application
replicaCount: 3
image:
repository: myapp
tag: "v2.1.0"
resources:
limits:
memory: 512Mi
cpu: 500m
ingress:
enabled: true
host: app.example.com
Use Helm when: You need to deploy an application to Kubernetes with configurable values across environments (dev/staging/prod).
Don't use Helm when: You need to create the Kubernetes cluster itself (use Terraform), configure the nodes (use Ansible), or manage cloud resources outside K8s (use Terraform).
The Overlap Zones (Where Arguments Happen)¶
"Should this be Terraform or Ansible?"¶
The grey area: configuring cloud resources that require SSH-like operations.
| Task | Best tool | Why |
|---|---|---|
| Create an EC2 instance | Terraform | API-driven cloud resource |
| Install packages on EC2 | Ansible | SSH-based server config |
| Create an RDS database | Terraform | API-driven cloud resource |
| Configure PostgreSQL settings | Ansible | Needs file edits + service restart |
| Create an S3 bucket | Terraform | API-driven |
| Upload files to S3 | Either | Terraform for static assets, Ansible for dynamic |
| Create IAM roles | Terraform | API-driven, needs state tracking |
| Manage user SSH keys on servers | Ansible | SSH-based server config |
The boundary: If you're calling a cloud provider API → Terraform. If you're SSH-ing into a server → Ansible.
"Should this be Helm or Kustomize?"¶
| Feature | Helm | Kustomize |
|---|---|---|
| Templating | Go templates (powerful, complex) | YAML patching (simple, limited) |
| Values per environment | values-prod.yaml, values-staging.yaml |
Overlay directories |
| Dependencies | Chart dependencies (sub-charts) | Resource composition |
| Packaging | Charts are versioned, distributable | Just files in a directory |
| Ecosystem | Huge (thousands of community charts) | Smaller (native to kubectl) |
Use Helm when: you need conditionals, loops, or community charts. Use Kustomize when: your YAML is simple and you want to avoid a templating layer.
The Complete Infrastructure Pipeline¶
Phase 1: Terraform
├── VPC, subnets, security groups
├── EKS cluster (or EC2 instances)
├── RDS database
├── S3 buckets
├── IAM roles
└── Route53 DNS records
Phase 2: Ansible (for non-K8s servers)
├── Install packages (nginx, monitoring agent)
├── Configure sshd, firewall, NTP
├── Create users, deploy SSH keys
├── Configure log rotation
└── Register with monitoring
Phase 3: Helm (for K8s workloads)
├── Deploy application (Deployment + Service + Ingress)
├── Deploy monitoring stack (Prometheus, Grafana)
├── Deploy log shipping (Fluentd/Fluent Bit)
└── Deploy cert-manager, external-secrets
Flashcard Check¶
Q1: Terraform is stateful. Ansible is stateless. What does this mean?
Terraform tracks what it created in a state file (knows "I created instance i-abc123"). Ansible doesn't track — it checks current state each run. This is why Terraform can destroy resources and Ansible can't easily.
Q2: Creating a VPC — Terraform or Ansible?
Terraform. It's an API-driven cloud resource that needs state tracking.
Q3: Installing nginx on a server — Terraform or Ansible?
Ansible. It's SSH-based server configuration.
Q4: Deploying an app to Kubernetes — which tool?
Helm (or Kustomize + kubectl). Not Terraform or Ansible (though both can do it awkwardly).
Q5: When would you use ALL THREE tools together?
Terraform creates the cloud infra (VPC, EKS, RDS). Ansible configures any non-K8s servers. Helm deploys applications to the EKS cluster. Each tool in its zone.
Cheat Sheet¶
Decision Matrix¶
| Task | Tool |
|---|---|
| Create cloud resources | Terraform |
| Configure servers (packages, files) | Ansible |
| Deploy K8s applications | Helm |
| Create K8s cluster | Terraform |
| Configure K8s nodes | Ansible |
| Manage K8s secrets | Helm + external-secrets |
| DNS records | Terraform |
| TLS certificates | cert-manager (in K8s) or Terraform (cloud) |
| CI/CD pipeline definition | Neither — use GitHub Actions/GitLab CI YAML |
Tool Properties¶
| Property | Terraform | Ansible | Helm |
|---|---|---|---|
| Language | HCL | YAML | Go templates + YAML |
| State | Explicit file (S3 + DynamoDB) | None (idempotent) | K8s secrets |
| Execution | Plan + Apply | Playbook run | Install/Upgrade |
| Rollback | terraform apply old config |
Re-run playbook | helm rollback |
| Dry run | terraform plan |
--check --diff |
helm template |
Takeaways¶
-
Terraform builds, Ansible configures, Helm deploys. Don't use a tool outside its sweet spot. Each one is excellent at its job and mediocre at the others'.
-
The boundary is the interface. Cloud API → Terraform. SSH → Ansible. Kubernetes API → Helm. When you're unsure, ask: "What interface am I using?"
-
State is the key difference. Terraform needs state to know what it manages. Ansible is stateless (checks current state each run). Helm stores release history in K8s secrets.
-
Use all three together. They're complementary, not competing. The pipeline is: Terraform → Ansible → Helm.
-
Overlap zones cause arguments. When two tools could work, pick based on the primary interface. If it's mostly cloud API with a small SSH part, use Terraform with a small Ansible follow-up — not Ansible trying to do cloud provisioning.
Exercises¶
-
Classify tasks by tool. For each task below, decide whether Terraform, Ansible, or Helm is the best fit: (a) create an AWS S3 bucket, (b) install nginx on an EC2 instance, (c) deploy a Prometheus stack to Kubernetes, (d) create a Route53 DNS record, (e) configure sshd_config on 50 servers, (f) manage a Kubernetes Ingress resource. Write one sentence justifying each choice based on the interface (cloud API, SSH, or Kubernetes API).
-
Write a minimal Terraform config. Create a
main.tfwith alocal_fileresource (no cloud provider needed):resource "local_file" "hello" { content = "Hello from Terraform" filename = "/tmp/tf-test.txt" }. Runterraform init,terraform plan, andterraform apply. Inspect the state file (terraform.tfstate). Delete the file manually, runterraform planagain, and observe that Terraform detects the drift and wants to recreate it. Clean up withterraform destroy. -
Write a minimal Ansible playbook. Create a playbook that installs a package and writes a file on localhost:
hosts: localhost, connection: local, tasks: [copy module to write /tmp/ansible-test.txt]. Run it twice withansible-playbook -v playbook.yml. Observe that the first run reports "changed" and the second reports "ok" — demonstrating Ansible's stateless idempotency. Delete the file and run again to see it re-created. -
Render a Helm template locally. If Helm is installed, add a public chart repo (
helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update). Render a chart without installing:helm template my-nginx bitnami/nginx --set replicaCount=2. Examine the generated YAML. ChangereplicaCountto 5 and render again. Compare the two outputs to see how Helm values translate to Kubernetes manifests. -
Map a real project to the three-tool pipeline. Take a project you work on (or an imaginary web app with a database, API, and frontend). Draw the infrastructure pipeline listing which resources Terraform would create (VPC, database, cluster), what Ansible would configure (server packages, OS settings), and what Helm would deploy (application containers, monitoring). Identify any tasks in the overlap zone and justify your tool choice for each.
Related Lessons¶
- The Terraform State Disaster — when Terraform state breaks
- Deploy a Web App From Nothing — building the full stack layer by layer
- What Happens When You
kubectl apply— what Helm submits to the API server