Skip to content

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

  1. 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'.

  2. The boundary is the interface. Cloud API → Terraform. SSH → Ansible. Kubernetes API → Helm. When you're unsure, ask: "What interface am I using?"

  3. 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.

  4. Use all three together. They're complementary, not competing. The pipeline is: Terraform → Ansible → Helm.

  5. 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

  1. 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).

  2. Write a minimal Terraform config. Create a main.tf with a local_file resource (no cloud provider needed): resource "local_file" "hello" { content = "Hello from Terraform" filename = "/tmp/tf-test.txt" }. Run terraform init, terraform plan, and terraform apply. Inspect the state file (terraform.tfstate). Delete the file manually, run terraform plan again, and observe that Terraform detects the drift and wants to recreate it. Clean up with terraform destroy.

  3. 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 with ansible-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.

  4. 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. Change replicaCount to 5 and render again. Compare the two outputs to see how Helm values translate to Kubernetes manifests.

  5. 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.


  • 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