When to Use Terraform? #
Terraform isn’t a universal solution for every infrastructure problem. There are scenarios where Terraform is the best choice you can make, scenarios where another tool is far more appropriate, and scenarios where forcing Terraform only adds unnecessary complexity. Understanding these boundaries matters so you don’t over-engineer a solution for a problem that’s actually simple — or, conversely, under-invest in infrastructure that’s already becoming critical.
This article covers in depth when and why you should use Terraform, when you should choose an alternative, how team size affects the decision, and how Terraform integrates with the broader DevOps tool ecosystem.
Decision Framework: Use It or Not? #
The decision to use Terraform should be based on specific needs, not hype. Here’s a decision framework you can use to evaluate whether Terraform fits your case.
flowchart TD
A["Do you manage\ncloud infrastructure?"] -->|"No"| B["Terraform NOT\nneeded"]
A -->|"Yes"| C["How many\nresources are managed?"]
C -->|"< 5 resources"| D["Scripts/CLI\nmight be enough"]
C -->|"5-50 resources"| E["Terraform is very\nhelpful"]
C -->|"> 50 resources"| F["Terraform is\nCRITICAL"]
E --> G["Needs to be repeated across\nmultiple environments?"]
F --> G
G -->|"Yes"| H["Terraform is\nHIGHLY SUITED"]
G -->|"No"| I["Still useful\nfor audit & plan"]
D --> J["Team of only 1 person?"]
J -->|"Yes"| K["Manual CLI\nis acceptable"]
J -->|"No"| L["Consider\nTerraform for\nconsistency"]
style H fill:#e8f5e9,stroke:#2e7d32
style F fill:#e8f5e9,stroke:#2e7d32
style B fill:#f5f5f5,stroke:#9e9e9e
style D fill:#fff3e0,stroke:#e65100
style K fill:#fff3e0,stroke:#e65100The framework above isn’t a hard rule — more of a general guideline. There are scenarios where even 3 resources are better managed with Terraform (for example, if those resources are critical and need to be recreatable quickly). There are also scenarios where 100 resources can be managed with another tool (for example, if all resources live in one AWS account managed by one person).
Scenarios Where Terraform Fits Best #
There are several scenarios where Terraform delivers the most value compared to alternatives:
1. Multi-Cloud Infrastructure #
When your infrastructure is spread across multiple cloud providers, Terraform becomes nearly irreplaceable. You can manage AWS, GCP, Azure, and even SaaS resources (Datadog, PagerDuty, GitHub) in one consistent workflow.
# Multi-cloud: AWS + GCP in a single configuration
provider "aws" {
region = "ap-southeast-1"
}
provider "google" {
project = "my-gcp-project"
region = "asia-southeast1"
}
# Web server on AWS
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
tags = { Name = "web-aws" }
}
# BigQuery dataset on GCP (for analytics)
resource "google_bigquery_dataset" "analytics" {
dataset_id = "web_analytics"
location = "asia-southeast1"
}
# DNS on Cloudflare (SaaS)
resource "cloudflare_record" "web" {
zone_id = var.cloudflare_zone_id
name = "app"
value = aws_instance.web.public_ip
type = "A"
}
# Monitoring on Datadog (SaaS)
resource "datadog_monitor" "web_health" {
name = "Web Server Health"
type = "service check"
message = "Web server is down! Notify @pagerduty"
query = "process.up('web-server').last(2).count_by_status()"
}
Imagine having to use CloudFormation for AWS, Deployment Manager for GCP, and each provider’s CLI for Cloudflare and Datadog. Four different tools with four different workflows — compared to a single terraform apply.
2. Infrastructure That Needs to Be Reproduced #
When you need to create identical environments (staging = production, or a new region = the old region), Terraform fits perfectly. Just run the same configuration with different parameters.
# module-environment/main.tf — A module that can be used to
# create identical environments
module "staging" {
source = "./modules/web-stack"
environment = "staging"
instance_type = "t3.small"
instance_count = 1
db_instance = "db.t3.medium"
db_multi_az = false
}
module "production" {
source = "./modules/web-stack"
environment = "production"
instance_type = "t3.large"
instance_count = 3
db_instance = "db.r5.large"
db_multi_az = true
}
Both environments are built from the same module. If staging works well, production will work too — because the code producing the infrastructure is identical.
3. Teams with Multiple Engineers #
When more than one person manages infrastructure, consistency becomes a critical problem. With Terraform, everyone uses the same code, review happens in Pull Requests, and changes are recorded in Git.
flowchart LR
subgraph Solo["Solo Developer"]
S1["1 Person, 1 Console"] --> S2["Manual CLI\nis enough"]
end
subgraph Team["Team of 2-5"]
T1["Several People\nSame Access"] --> T2["Need Consistency"]
T2 --> T3["Terraform\n+ Git Workflow"]
end
subgraph Large["Large Team 5+"]
L1["Many People\nMany Services"] --> L2["Need Standardization"]
L2 --> L3["Terraform\n+ Modules\n+ CI/CD Pipeline"]
end
Solo --> S3["⚠️ Risk: single\npoint of failure"]
Team --> T4["✓ Audit trail\n✓ Code review"]
Large --> L5["✓ Self-service\n✓ Governance\n✓ Compliance"]
style S3 fill:#ffebee,stroke:#c62828
style T4 fill:#e8f5e9,stroke:#2e7d32
style L5 fill:#e8f5e9,stroke:#2e7d324. Compliance and Audit Requirements #
When your organization must meet compliance standards (SOC2, ISO 27001, PCI-DSS), the ability to prove who changed what, when, and why is a must. Git + Terraform provides a complete audit trail.
# The auditor asks: "Who opened port 22 to the public?"
# The answer is in the git log:
$ git log --oneline security-groups.tf
f4a5b6c Close port 22 from public — audit remediation (#198)
a1b2c3d Open port 22 for temporary troubleshooting (#189)
# Traceable: who (git author), when (timestamp),
# why (commit message + PR description)
5. Disaster Recovery and Business Continuity #
When your infrastructure is defined in code, disaster recovery changes from “days of manual reconstruction” to “run terraform apply in a new region in minutes.”
# Change one variable to fail over to another region
variable "aws_region" {
description = "AWS region for deployment"
type = string
default = "ap-southeast-1" # Change to ap-northeast-1 for DR
}
provider "aws" {
region = var.aws_region
}
# The entire infrastructure will be created in the new region
# with identical configuration
Scenarios Where Terraform Is NOT the Right Fit #
Knowing when not to use Terraform is just as important as knowing when to use it.
1. Server Configuration (Configuration Management) #
Terraform has provisioners like remote-exec and file for configuring servers, but that isn’t its strength. Ansible, Chef, or Puppet are far more appropriate for this task.
// ANTI-PATTERN: Using Terraform remote-exec for server configuration
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
// DON'T DO THIS:
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl start nginx",
"sudo systemctl enable nginx",
// ... 50 more lines of configuration
// Not idempotent, no rollback
// Very limited error handling
]
}
}
// CORRECT: Use Terraform for provisioning,
// Ansible for configuration
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
tags = { Name = "web-server" }
}
// Then run a separate Ansible playbook
// to configure the server
2. Application Deployment #
Deploying applications (build, test, push images, update services) is a CI/CD process better handled by tools like GitHub Actions, GitLab CI, or Jenkins.
# CORRECT: Deployment handled by a CI/CD pipeline
# (GitHub Actions example)
name: Deploy Application
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
docker build -t app:${{ github.sha }} .
docker push registry.com/app:${{ github.sha }}
- name: Update Kubernetes deployment
run: |
kubectl set image deployment/app \
app=registry.com/app:${{ github.sha }}
3. Very Simple Infrastructure #
If you only have 1-2 resources and no plans to grow, the overhead of writing and maintaining Terraform configs may not be worth the benefits.
WHEN MANUAL CLI IS STILL ACCEPTABLE:
✓ Only 1-2 resources (e.g. 1 VPS + 1 domain)
✓ No need to repeat across other environments
✓ Managed by 1 person
✓ No compliance requirements
✓ Prototype / side project / hackathon
WHEN TO MOVE TO TERRAFORM SOON:
✗ You start needing a second environment (staging)
✗ The team grows
✗ Resources grow past 5
✗ Compliance requirements appear
✗ You want disaster recovery capability
4. Pure Container Orchestration #
If your entire workload runs on Kubernetes and you don’t manage the cluster itself (managed Kubernetes), tools like Helm, Kustomize, or ArgoCD might be more appropriate.
// ANTI-PATTERN: Using Terraform to deploy to an existing K8s
// cluster
resource "kubernetes_deployment" "app" {
metadata {
name = "my-app"
}
spec {
replicas = 3
// ... full deployment definition
}
}
// Problem: terraform plan always shows changes
// because the K8s controller modifies the desired state
// Better to use Helm or Kustomize for K8s workloads
Team Size Considerations #
Team size greatly influences how you use Terraform — not just whether you use it, but how the workflow looks.
Solo Developer #
A solo developer can use Terraform in the simplest way: one directory, one state file, straight terraform apply from a laptop.
# Solo developer workflow
$ terraform init
$ terraform plan
$ terraform apply
# Simple enough, no CI/CD pipeline needed
# State locally or in S3
However, even for a solo developer there’s a big benefit: the ability to destroy and recreate the entire infrastructure with a single command. This is very useful for experiments, testing, and disaster recovery.
Small Team (2-5 People) #
Small teams need better coordination. Terraform + Git + Remote State is the minimum combination.
SMALL TEAM WORKFLOW:
1. Engineer A creates a branch "add-monitoring"
2. Engineer A adds monitoring resources in Terraform
3. Engineer A runs terraform plan locally
4. Engineer A opens a Pull Request with the plan output
5. Engineer B reviews the PR + plan
6. Merge → CI/CD runs terraform apply
MINIMUM REQUIREMENTS:
✓ Remote state (S3 + DynamoDB locking)
✓ Git repository for configuration
✓ Pull Request review process
✓ terraform plan in the PR (CI/CD or manual)
Medium Team (5-15 People) #
Medium teams need stronger standardization — shared modules, naming conventions, and possibly Terragrunt for DRY configuration.
// terragrunt.hcl — DRY configuration for multi-environment
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
terraform {
source = "../../modules//web-stack"
}
Large Team (15+ People) #
Large teams need a dedicated platform team to manage Terraform — building modules, setting policies, and providing self-service for developers.
flowchart TD
subgraph Platform["Platform Team"]
P1["Maintain Terraform\nModules"]
P2["Set Policy\n(OPA/Sentinel)"]
P3["Manage CI/CD\nPipeline"]
P4["Provide\nSelf-Service"]
end
subgraph Dev["Developer Teams"]
D1["Team A:\nWeb Application"]
D2["Team B:\nData Pipeline"]
D3["Team C:\nMobile Backend"]
end
Platform -->|"Module + Policy"| Dev
Dev -->|"PR + terraform plan"| Platform
D1 --> S1["Use module:\nmodule "web" {\n source = "company/web/aws"\n}"]
D2 --> S2["Use module:\nmodule "pipeline" {\n source = "company/data/aws"\n}"]
D3 --> S3["Use module:\nmodule "mobile" {\n source = "company/api/aws"\n}"]
style Platform fill:#e3f2fd,stroke:#1565c0
style Dev fill:#e8f5e9,stroke:#2e7d32Terraform in the DevOps Ecosystem #
Terraform isn’t a standalone tool — it’s part of a larger DevOps ecosystem. Understanding how Terraform interacts with other tools helps you design efficient workflows.
Terraform + CI/CD #
# GitHub Actions: Terraform Plan on every PR
name: Terraform
on:
pull_request:
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform/
- name: Terraform Plan
run: terraform plan -out=tfplan
working-directory: terraform/
- name: Comment PR with Plan
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const plan = execSync('terraform show -no-color tfplan', {
cwd: 'terraform/'
}).toString();
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: '## Terraform Plan\n```\n' + plan + '\n```'
});
Terraform + Monitoring #
# After Terraform creates the resource, automatically add monitoring
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
tags = { Name = "web-server", Environment = "production" }
}
# CloudWatch alarm — part of the same infrastructure
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
alarm_name = "web-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 80
alarm_description = "CPU utilization exceeds 80%"
dimensions = {
InstanceId = aws_instance.web.id
}
alarm_actions = [aws_sns_topic.alerts.arn]
}
Terraform + Kubernetes #
Terraform can create Kubernetes clusters (EKS, GKE, AKS), but for managing workloads inside the cluster, Helm or Kustomize are more appropriate.
# Terraform: Create an EKS cluster
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = "my-cluster"
cluster_version = "1.28"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
eks_managed_node_groups = {
general = {
desired_size = 3
min_size = 2
max_size = 5
instance_types = ["t3.large"]
}
}
}
# Helm: Deploy the application to the cluster Terraform created
provider "helm" {
kubernetes {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
}
}
resource "helm_release" "nginx_ingress" {
name = "nginx-ingress"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
namespace = "ingress-nginx"
create_namespace = true
set {
name = "controller.replicaCount"
value = "2"
}
}
flowchart TD
subgraph TerraformScope["Managed by Terraform"]
T1["VPC, Subnet, Security Groups"]
T2["EKS/GKE/AKS Cluster"]
T3["RDS, S3, ElastiCache"]
T4["IAM Roles & Policies"]
T5["DNS, CDN, Certificate"]
end
subgraph HelmScope["Managed by Helm/Kustomize"]
H1["Application Deployment"]
H2["Ingress Controller"]
H3["Monitoring Stack"]
H4["Service Mesh"]
end
subgraph CI_CDScope["Managed by CI/CD"]
C1["Build & Test"]
C2["Push Image"]
C3["Update Deployment"]
C4["Run Migrations"]
end
TerraformScope -->|"Cluster alive"| HelmScope
HelmScope -->|"App deployed"| CI_CDScope
style TerraformScope fill:#e3f2fd,stroke:#1565c0
style HelmScope fill:#e8f5e9,stroke:#2e7d32
style CI_CDScope fill:#fff3e0,stroke:#e65100Terraform Usage Anti-Patterns #
Here are some anti-patterns that commonly occur when using Terraform:
Anti-Pattern 1: Using Terraform for Everything #
ANTI-PATTERN:
✗ Deploying apps to K8s with kubernetes_deployment resources
✗ Configuring servers with remote-exec provisioners
✗ Managing DNS records that change daily (API rate limits)
✗ Managing database data seeding
WHY IT'S DANGEROUS:
✓ terraform plan always shows changes
✓ Very limited error handling for complex operations
✓ Blurs the boundaries of what each tool is responsible for
Anti-Pattern 2: Not Using Modules #
ANTI-PATTERN:
✗ Copy-pasting resource blocks for every environment
✗ Changing parameters manually across environments
✗ No naming convention standardization
WHY IT'S DANGEROUS:
✓ Changes in one environment forget to propagate to others
✓ Inconsistency between environments
✓ Code duplication that's hard to maintain
CORRECT:
✓ Create a module for each infrastructure component
✓ Parameterize with variables
✓ Share modules through a registry
Anti-Pattern 3: Local State for a Team #
ANTI-PATTERN:
✗ State files on each engineer's laptop
✗ No state locking
✗ Pushing state files to Git (contains secrets!)
CORRECT:
✓ Remote state in S3/GCS/Azure Blob
✓ State locking with DynamoDB/consul
✓ State files are NEVER committed to Git
Summary #
- Use Terraform for multi-cloud infrastructure, reproducible environments, teams that need consistency, compliance requirements, and disaster recovery.
- Don’t use Terraform for server configuration (use Ansible/Chef/Puppet), application deployment (use CI/CD), or pure Kubernetes workloads (use Helm/Kustomize).
- Solo developers can start simple — one directory, local state, apply directly. But move to remote state as soon as a team forms.
- Small teams need remote state + Git workflow + PR review. That’s the minimum effective combination.
- Large teams need a dedicated platform team, module registry, policy as code (OPA/Sentinel), and self-service for developers.
- Terraform isn’t a standalone tool — it integrates with CI/CD, monitoring, Kubernetes, and other DevOps tools for a complete workflow.
- Avoid anti-patterns: don’t use Terraform for everything, always use modules, and keep state in a remote backend with locking.