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:#e65100

The 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:#2e7d32

4. 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 &quot;web&quot; {\n  source = &quot;company/web/aws&quot;\n}"]
    D2 --> S2["Use module:\nmodule &quot;pipeline&quot; {\n  source = &quot;company/data/aws&quot;\n}"]
    D3 --> S3["Use module:\nmodule &quot;mobile&quot; {\n  source = &quot;company/api/aws&quot;\n}"]

    style Platform fill:#e3f2fd,stroke:#1565c0
    style Dev fill:#e8f5e9,stroke:#2e7d32

Terraform 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:#e65100

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

← Previous: Terraform Alternatives   Next: Declarative →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact