Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Terraform vs Ansible: Provisioning vs Configuration Management

By Kokil Thapa | Last reviewed: August 2026

Choosing between infrastructure tools is a foundational decision that dictates how reliably you can deploy and maintain production systems. Understanding Terraform vs Ansible: Provisioning vs Configuration Management prevents the costly mistake of using a hammer to drive a screw. While both are essential Infrastructure as Code (IaC) tools, they solve fundamentally different problems in the deployment lifecycle. For developers managing cloud hosting services in Nepal or global infrastructure, distinguishing between creating resources and configuring them is the first step toward stable operations.

How does Terraform vs Ansible: Provisioning vs Configuration Management differ architecturally?

The core distinction lies in their primary operational focus. Terraform is a declarative provisioning tool designed to create, modify, and destroy cloud infrastructure through API calls. You define the desired end state of your infrastructure in HCL (HashiCorp Configuration Language), and Terraform calculates the execution plan to reach that state. It maintains a state file that tracks every resource ID, IP address, and dependency, making it authoritative for infrastructure topology.

Ansible, conversely, is a procedural-hybrid configuration management tool. It connects to existing servers via SSH (or WinRM) and executes tasks sequentially to ensure the system matches your desired configuration. While Ansible can provision some cloud resources via modules, it lacks the sophisticated dependency graph and state tracking that makes Terraform reliable for complex infrastructure topologies. In my experience deploying Laravel applications across multiple environments, attempting to manage VPCs, subnets, and load balancers purely through Ansible leads to fragile scripts that break when cloud provider APIs change or when partial failures occur.

Terraform (Provisioning)HCL Config FilesState File (.tfstate)Cloud Provider APIVPC / Subnet / EC2RDS / S3 / IAMCreates & Tracks InfrastructureAnsible (Configuration)Playbooks (YAML)Inventory FileSSH / WinRM AgentInstall PHP 8.4Configure NginxConfigures Existing Servers
Terraform provisions infrastructure via APIs while Ansible configures servers via SSH — understanding this separation is key to effective IaC

This architectural difference means Terraform treats infrastructure as immutable by default. When you need to change a server's base image or instance type, Terraform destroys the old resource and creates a new one. Ansible treats servers as mutable pets, applying incremental changes to running systems. Both approaches have merit, but conflating them creates maintenance nightmares. On production legal-tech portals I've built, we provision AWS infrastructure with Terraform and then hand off server IPs to Ansible for application deployment and hardening.

When should you use Terraform for infrastructure provisioning?

Terraform is the correct choice when you need to manage cloud resources that have lifecycle dependencies. This includes VPCs, subnets, security groups, managed databases, object storage buckets, IAM policies, DNS records, and Kubernetes clusters. The tool's strength lies in its ability to model complex dependency graphs and execute changes in the correct order automatically.

Core provisioning scenarios for Terraform

  • Multi-cloud infrastructure: Managing resources across AWS, Azure, GCP, or DigitalOcean with consistent workflows
  • Network topology: Creating VPCs, peering connections, VPN gateways, and route tables with proper ordering
  • Managed services: Provisioning RDS, ElastiCache, S3, CloudFront, or equivalent PaaS offerings
  • IAM and security: Defining roles, policies, and access controls as version-controlled code
  • Environment parity: Replicating identical infrastructure stacks across dev, staging, and production

In 2026, Terraform 1.9+ supports advanced features like deferred actions and improved provider mocking for testing. For teams building Laravel applications that require specific infrastructure prerequisites (Redis clusters, PostgreSQL with extensions, S3-compatible storage), Terraform ensures these dependencies exist before application deployment begins. A common mistake is trying to create an RDS instance via Ansible's rds_instance module; while technically possible, you lose state tracking, drift detection, and the ability to safely destroy the database when tearing down an environment.

# main.tf - Terraform provisioning example (Terraform 1.9+, AWS Provider 5.x)
terraform {
  required_version = ">= 1.9.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

resource "aws_instance" "laravel_app" {
  ami           = "ami-0c55b159cbfafe1f0" # Ubuntu 24.04 LTS
  instance_type = "t3.medium"
  subnet_id     = aws_subnet.private.id

  vpc_security_group_ids = [aws_security_group.app_sg.id]

  tags = {
    Name        = "laravel-app-server"
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

output "app_server_ip" {
  value       = aws_instance.laravel_app.private_ip
  description = "Private IP for Ansible inventory"
}

This Terraform configuration creates an EC2 instance and exposes its private IP as an output. That output can be consumed by downstream tooling, including Ansible dynamic inventories, closing the loop between provisioning and configuration without manual intervention.

When should you use Ansible for configuration management?

Ansible shines when you need to configure operating systems, install software packages, manage users, deploy application code, and perform ongoing maintenance on running servers. Its agentless architecture (using SSH) makes it ideal for brownfield environments where installing agents is impractical or prohibited.

Configuration management scenarios for Ansible

  1. OS hardening: Applying CIS benchmarks, configuring firewalls, managing SSH settings
  2. Application runtime: Installing PHP 8.4, Nginx, Composer dependencies, setting up PHP-FPM pools
  3. User and access management: Creating system users, managing sudoers, rotating SSH keys
  4. Application deployment: Pulling code from Git, running migrations, clearing caches, restarting services
  5. Patch management: Running security updates, rebooting servers in rolling batches

For DevOps automation in Nepal, where teams often manage heterogeneous server fleets across local data centers and cloud providers, Ansible's flexibility is invaluable. Unlike Terraform, which expects to own the entire lifecycle of a resource, Ansible can be run ad-hoc against any reachable server. This makes it perfect for emergency patches, one-off configuration changes, or managing legacy servers that predate your IaC adoption.

playbook.ymlDefine TasksInventoryHosts & GroupsAnsible ControllerParse & PlanSSH ConnectionAgentless ExecutionServer ATasks AppliedServer BTasks Applied
Ansible parses playbooks and inventory, connects via SSH, and applies configuration tasks to target servers without requiring agents
# laravel-setup.yml - Ansible playbook for PHP 8.4 + Nginx (Ansible 2.17+)
---
- name: Configure Laravel Application Server
  hosts: laravel_apps
  become: true
  vars:
    php_version: "8.4"
    app_path: "/var/www/laravel-app"

  tasks:
    - name: Install PHP 8.4 and required extensions
      ansible.builtin.apt:
        name:
          - "php{{ php_version }}-fpm"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-redis"
          - "php{{ php_version }}-mbstring"
          - nginx
        state: present
        update_cache: true

    - name: Deploy application code from Git
      ansible.builtin.git:
        repo: "git@github.com:example/laravel-app.git"
        dest: "{{ app_path }}"
        version: "main"
      notify: Restart PHP-FPM

    - name: Install Composer dependencies
      community.general.composer:
        command: install
        working_dir: "{{ app_path }}"
        no_dev: true
        optimize_autoloader: true

  handlers:
    - name: Restart PHP-FPM
      ansible.builtin.systemd:
        name: "php{{ php_version }}-fpm"
        state: restarted

This playbook demonstrates Ansible's strength: expressing server configuration as repeatable, version-controlled tasks. Notice the handler pattern — PHP-FPM only restarts when code actually changes, preventing unnecessary service interruptions during routine runs.

How do Terraform and Ansible integrate in production workflows?

The most resilient infrastructure pipelines use both tools in sequence, not as alternatives. Terraform provisions the infrastructure and outputs connection details; Ansible consumes those details to configure the provisioned resources. This separation respects each tool's strengths and avoids the anti-pattern of forcing one tool to do the other's job.

Integration patterns that work in practice

PatternHow It WorksBest ForCaveats
Terraform Output → Static InventoryTerraform writes IPs to a file Ansible readsSimple, single-environment setupsManual refresh needed after infra changes
Terraform Dynamic Inventory PluginAnsible queries Terraform state directlyMulti-environment, auto-scaling groupsRequires consistent tagging strategy
Terraform Provisioner (remote-exec)Terraform runs shell commands post-createBootstrap only (install Python/SSH keys)Not for full configuration; breaks idempotency
CI/CD Pipeline OrchestrationGitLab CI runs Terraform apply, then Ansible playbookProduction deployments with audit trailsPipeline complexity increases; needs secret management

On sister sites sharing a Deployer 7 + GitLab CI pipeline, we use the CI/CD orchestration pattern exclusively. Terraform runs first in the pipeline, creating or updating infrastructure. Its outputs (server IPs, database endpoints) are passed as artifacts to the Ansible stage. This ensures configuration never runs against stale or non-existent infrastructure. The key insight is treating the integration point as a contract: Terraform guarantees certain resources exist with specific attributes, and Ansible trusts that contract rather than re-verifying infrastructure state.

GitLab CI TriggerMerge to main branchStage 1: Terraformterraform initterraform planterraform applyOutputs: IPs, EndpointsArtifactstf_output.jsonStage 2: AnsibleRead artifactsGenerate inventoryansible-playbookConfigure serversResult: Infrastructure provisioned AND configured in single pipeline runRollback possible via terraform destroy + redeploy
Production CI/CD pipeline integrating Terraform provisioning and Ansible configuration with artifact-based handoff for reliable deployments

A critical gotcha: never let Ansible manage resources that Terraform owns. If Terraform creates a security group and Ansible modifies its rules, the next Terraform run will either revert the changes or fail with a conflict. Establish clear ownership boundaries and document them. In my experience, teams that violate this boundary spend more time debugging tool conflicts than shipping features.

What are the key differences in state management and idempotency?

State management is where the philosophical divide between these tools becomes operationally significant. Terraform maintains an explicit state file (terraform.tfstate) that records every managed resource's current attributes. Before making changes, it compares desired state (your HCL) against actual state (the state file refreshed against the cloud API). This enables safe destruction, dependency-aware updates, and drift detection.

Ansible has no persistent state file. It determines the current state of a system by querying it at runtime (checking if a package is installed, if a file exists, if a service is running). This makes Ansible inherently idempotent at the task level — running the same playbook twice produces the same result. However, it cannot detect external drift. If someone manually installs a package or modifies a config file outside Ansible, the next playbook run will correct it, but Ansible has no record of the deviation occurring.

For infrastructure where compliance and auditability matter (legal-tech platforms handling sensitive documents, financial systems), Terraform's state provides an auditable trail of what was provisioned, when, and by whom. Store state remotely in S3, GCS, or Terraform Cloud with locking enabled to prevent concurrent modifications. Ansible's lack of state makes it unsuitable for managing resources where accidental deletion would be catastrophic, but ideal for configurations where convergence to desired state is the goal regardless of how the system arrived at its current condition.

Making the Right Choice for Your Infrastructure Stack

The decision between Terraform vs Ansible: Provisioning vs Configuration Management isn't mutually exclusive — it's complementary. Use Terraform to define and manage your infrastructure foundation: networks, compute instances, databases, and cloud-native services. Use Ansible to configure those resources once they exist: installing runtimes, deploying applications, managing secrets, and performing maintenance. Resist the temptation to make one tool do both jobs; the short-term convenience costs long-term reliability.

For teams evaluating their infrastructure automation strategy, start by auditing your current pain points. If provisioning new environments takes days of manual clicking, adopt Terraform first. If server configurations drift and deployments are inconsistent, prioritize Ansible. If both problems exist, implement the CI/CD integration pattern described above. The goal isn't tool purity — it's predictable, repeatable infrastructure that supports your application delivery. Whether you're building CI/CD pipelines for a Kathmandu startup or managing multi-region infrastructure for a global SaaS, respecting each tool's purpose yields systems that survive contact with production reality.

If you need hands-on guidance implementing Terraform, Ansible, or integrated IaC pipelines for your production environment, reach out to discuss your infrastructure needs. Practical experience beats theoretical best practices every time.

Frequently Asked Questions

Terraform provisions infrastructure like servers and networks using declarative code. Ansible configures software and applications on existing servers using procedural tasks.

Yes, but it lacks state management and drift detection. Use Terraform for immutable infrastructure creation and Ansible for configuring the resulting resources.

Terraform manages cluster infrastructure; Ansible configures nodes. Neither replaces Helm or Kustomize for application deployment inside Kubernetes clusters.

In my experience shipping legal-tech portals and eCommerce systems, I use Terraform to provision AWS EC2 instances and RDS databases, then Ansible to configure Ubuntu, PHP-FPM, and Nginx. Small Nepali businesses often lack dedicated DevOps staff, so keeping infrastructure code separate from configuration simplifies handovers. Terraform handles the immutable cloud layer while Ansible manages repeatable server setup, reducing manual errors during maintenance cycles common in local SMB environments.

Terraform maintains a state file tracking every managed resource, enabling drift detection and safe updates. Ansible is stateless by default, executing tasks idempotently without recording what changed previously. This distinction matters in production: if someone manually modifies an AWS security group, Terraform detects and reverts it on next apply. Ansible simply reapplies desired configuration without knowing prior state. For Nepal Gift Card and similar platforms, I store Terraform state in S3 with DynamoDB locking to prevent concurrent corruption during team deployments.

Both tools are open-source and free. Costs arise from cloud resources they manage and optional enterprise features. HashiCorp Cloud Platform offers managed Terraform at ~USD 70/month (Rs 9,300), while Red Hat Ansible Automation Platform starts ~USD 5,000/year. For most Nepal-based projects, self-hosted open-source versions suffice. The real expense is engineer time learning HCL versus YAML. I typically budget 2-3 weeks for teams new to either tool before expecting production proficiency.

Run Terraform first to create infrastructure, output inventory dynamically, then invoke Ansible playbooks against those hosts. In GitLab CI pipelines I maintain for sister sites like notarykathmandu.com and translationnepal.com, the deploy job executes terraform apply, generates an Ansible inventory from terraform output, runs ansible-playbook for server hardening and PHP setup, then triggers Deployer 7 for zero-downtime Laravel releases. This sequential approach ensures infrastructure exists before configuration begins, preventing race conditions and failed deployments.

Timing issues occur when Terraform reports a resource ready before services actually start. Always add health checks or wait_for modules in Ansible. Another pitfall is credential leakage: never embed secrets in Terraform outputs passed to Ansible. Use Vault or environment variables instead. I have also seen teams struggle with Python version mismatches on target hosts breaking Ansible modules. Pin Python versions explicitly in your Terraform user-data scripts to ensure Ansible compatibility across all provisioned instances.

Terraform integrates with HashiCorp Vault, AWS Secrets Manager, or SSM Parameter Store for injecting secrets at plan/apply time. Ansible uses ansible-vault for encrypting sensitive variables within playbooks. In production Laravel applications, I prefer storing secrets outside both tools entirely, fetching them at runtime via environment variables injected during deployment. This avoids committing encrypted vault files to Git and eliminates decryption key distribution problems. For Nepal payment gateway integrations like eSewa or Khalti, API keys live in .env files managed by Deployer, not in Terraform state or Ansible vars.

Choose Pulumi if your team prefers TypeScript, Python, or Go over HCL and wants unified provisioning plus configuration in one language. Pulumi eliminates context-switching between Terraform and Ansible but has a smaller ecosystem and fewer Nepal-relevant examples. For legal-tech portals requiring rapid iteration, I still prefer Terraform plus Ansible because HCL forces explicit resource declarations that reduce accidental deletions. Pulumi suits greenfield startups with strong software engineering culture; established agencies maintaining diverse client portfolios benefit more from Terraform's maturity and Ansible's vast module library.

Use remote backends with native locking: S3 with DynamoDB for AWS, Azure Blob Storage for Azure, or GCS for Google Cloud. Never store state locally or in Git. On shared EC2 infrastructure serving multiple Nepal legal-tech sites, I configure S3 backend with DynamoDB table named terraform-locks. Each workspace gets its own state prefix to prevent cross-project interference. Enable versioning on the S3 bucket for rollback capability. If a lock persists after a crashed apply, use terraform force-unlock cautiously after verifying no other process is actively modifying infrastructure.

Apply least-privilege IAM roles for both tools. Never use root credentials. Encrypt state files containing sensitive attributes. Scan Terraform plans with checkov or tfsec before applying. For Ansible, disable gather_facts when unnecessary to reduce attack surface. In my production deployments, Terraform runs in CI with read-only planning permissions; only merge-to-main triggers apply with elevated rights. Ansible connects via SSH keys rotated quarterly, never passwords. All secrets for Nepal payment integrations are fetched at runtime, never baked into AMIs or playbook variables committed to repositories.

First run terraform plan to see proposed changes without applying. Check state consistency with terraform refresh. If resources were modified outside Terraform, import them or update state manually using terraform state mv. Common failures include API rate limits, insufficient IAM permissions, or dependency ordering issues. Add explicit depends_on when implicit dependencies fail. For Nepal-based AWS deployments, verify VPC quotas and EC2 instance availability in ap-south-1 region. Always review CloudTrail logs when Terraform reports permission errors that seem incorrect; sometimes SCP policies override IAM roles silently.

Partially, using provisioners like remote-exec or cloud-init user_data. However, this couples configuration to provisioning, making updates risky and testing difficult. Provisioners cannot be re-run independently without recreating resources. I reserve Terraform provisioners only for bootstrapping Ansible itself, such as installing Python or creating initial users. All subsequent configuration happens through dedicated Ansible playbooks triggered post-apply. This separation allows reconfiguring servers without touching infrastructure state, essential for maintaining uptime on production eCommerce sites like Petals Nepal where redeploying EC2 instances would disrupt active orders.

Neither tool provides runtime monitoring. Terraform knows infrastructure exists; Ansible knows configuration applied successfully. Neither tracks application health, response times, or error rates post-deployment. Always pair them with dedicated observability stacks: Prometheus and Grafana for metrics, Loki for logs, or managed alternatives like Datadog. In my Laravel deployments, I add Ansible tasks to install and configure node exporters and log shippers after base configuration completes. Terraform provisions the monitoring infrastructure itself. This layered approach ensures you detect failures that neither provisioning nor configuration tools can see, especially critical for Nepal legal-tech portals handling sensitive client submissions.

Share this article

Quick Contact Options
Choose how you want to connect me: