Terraform vs Pulumi vs Ansible: IaC for small teams
You're a team of three engineers. One of you just broke staging by running a script manually. Nobody knows what the current state of infra actually is. IaC is the obvious fix โ but Terraform, Pulumi, and Ansible all claim to solve this. Which one? The short answer: they're not the same kind of tool,

You're a team of three engineers. One of you just broke staging by running a script manually. Nobody knows what the current state of infra actually is. IaC is the obvious fix โ but Terraform, Pulumi, and Ansible all claim to solve this. Which one? The short answer: they're not the same kind of tool, and the right choice depends on your team's profile more than the tools' feature lists. It's worth being precise about categories because "IaC" gets used too loosely: Terraform (HashiCorp, now BSL licensed): declarative, state-file-based cloud provisioning. You describe desired state; Terraform computes the diff and applies it. Pulumi: same concept as Terraform, but you write real code (Python, Go, TypeScript) instead of HCL. Ansible: imperative, agentless configuration management. Strong for day-2 ops (installing packages, configuring services), weaker for provisioning. These aren't identical tools competing for the same niche. Ansible is solid for: configuring existing servers, deploying applications, running ordered multi-step procedures across a fleet. It's awkward for: managing cloud resources from scratch. The modules exist (for EC2, VPCs, RDS), but state management is painful. Ansible doesn't track what it created โ each run answers "did this task succeed?", not "does this resource still exist?". You end up with orphaned resources and no clear inventory. For a small team provisioning cloud infra, Ansible alone isn't sufficient. The pattern that actually works: provision with Terraform, configure with Ansible. # playbook.yml โ configure a Python app on a freshly provisioned EC2 instance - name: Deploy Python app hosts: web become: yes tasks: - name: Install system dependencies apt: name: [python3, python3-pip, nginx] state: present update_cache: yes - name: Deploy application files copy: src: ./app/ dest: /opt/myapp/ owner: www-data - name: Install Python requirements pip: requirements: /opt/myapp/requirements.txt executable: pip3 - name: Enable and start service systemd: name: myapp state: started enabled: yes This works well. But you need Terraform or Pulumi to create that instance in the first place. Terraform has been the standard for cloud provisioning for years. HCL is learnable in a day, the provider ecosystem covers AWS, GCP, Azure, Cloudflare, GitHub, Datadog, and hundreds more. The plan/apply workflow gives you an explicit diff to review before any change lands. For a small team, the main advantages: Anyone can read and write HCL without a deep programming background terraform plan output is reviewable in PRs โ non-engineers can spot obvious regressions State locking via S3 + DynamoDB prevents concurrent applies from corrupting state The BSL license change only affects vendors building Terraform-as-a-service; internal use remains free The friction: HCL is not a real programming language. Complex logic via for_each, dynamic blocks, and conditionals becomes unwieldy at scale No native test framework โ you need Terratest or heavy terraform validate discipline State drift is a real burden: manual cloud console changes require terraform import or state rm # main.tf โ minimal VPC + EC2 for a Go API on AWS resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = { Name = "main" } } resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "eu-west-1a" } resource "aws_security_group" "api" { name = "api-sg" vpc_id = aws_vpc.main.id ingress { from_port = 8080 to_port = 8080 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_instance" "api" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.small" subnet_id = aws_subnet.public.id vpc_security_group_ids = [aws_security_group.api.id] tags = { Name = "go-api" } } Readable, reviewable, and straightforward for any team member โ including whoever joined last month. Pulumi replaces HCL with actual programming languages. The same stack in Python: import pulumi import pulumi_aws as aws vpc = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16", tags={"Name": "main"} ) subnet = aws.ec2.Subnet("public", vpc_id=vpc.id, cidr_block="10.0.1.0/24", availability_zone="eu-west-1a" ) sg = aws.ec2.SecurityGroup("api-sg", vpc_id=vpc.id, ingress=[{ "from_port": 8080, "to_port": 8080, "protocol": "tcp", "cidr_blocks": ["0.0.0.0/0"] }], egress=[{ "from_port": 0, "to_port": 0, "protocol": "-1", "cidr_blocks": ["0.0.0.0/0"] }] ) instance = aws.ec2.Instance("go-api", ami="ami-0c55b159cbfafe1f0", instance_type="t3.small", subnet_id=subnet.id, vpc_security_group_ids=[sg.id], tags={"Name": "go-api"} ) pulumi.export("instance_id", instance.id) The real appeal: you can loop over a list of environments without count gymnastics, write unit tests with pytest, reuse Python functions, and import your own internal libraries. For infra with 10+ environments or heavily dynamic configuration, Pulumi's expressiveness is a genuine win. The tradeoffs: State goes to Pulumi Cloud by default โ there's a free tier, but self-hosting requires more setup than Terraform's S3 backend Steeper onboarding: new team members need to know the language and Pulumi's async resource graph model. Debugging Output[T] types is not obvious Smaller community: Stack Overflow coverage and available modules are thinner than Terraform's Use Ansible when you're configuring existing servers, not provisioning from scratch. Use Terraform when: Your team has mixed backgrounds โ not everyone writes Python or Go fluently Your infra is relatively stable: fewer than five environments, no heavy dynamic resource generation You want the largest provider ecosystem and the most community resources Use Pulumi when: Your team is developer-heavy and HCL feels like a cognitive tax You need real programming constructs: dynamic loops, proper unit tests, shared libraries across stacks You're comfortable with Pulumi Cloud pricing or willing to configure a self-hosted state backend For most teams under five engineers, Terraform is the right starting point. The plan/apply review workflow fits naturally into PRs, HCL is fast to learn, and you won't hit its limits until your infra grows significantly. One thing that matters regardless of tool: gate every apply behind CI/CD with mandatory code review on infra changes. A misconfiguration merged without review is where incidents start. We maintain a security checklist for IaC pipelines covering state file access control, least-privilege IAM for CI runners, and safe secrets handling in both Terraform and Pulumi. Terraform, Pulumi, and Ansible aren't direct competitors โ they solve overlapping but distinct problems. Ansible handles configuration; Terraform and Pulumi handle provisioning. Between the two provisioning tools, Terraform wins on simplicity and ecosystem breadth; Pulumi wins when your infra logic outgrows what HCL can express cleanly. Pick based on your team's actual skills and infra complexity, not on which tool had the best talk at the last DevOps conference. I run AYI NEDJIMI Consultants, a cybersecurity consulting firm. We publish free security hardening checklists โ PDF and Excel.
Key Takeaways
- โขYou're a team of three engineers
- โขThis story was reported by Dev.to, covering developments in the dev space.
- โขAI advancements continue to reshape industries โ read the full article on Dev.to for complete coverage.
๐ Continue reading the full article:
Read Full Article on Dev.to โShare this article



