Devops

Terraform Provisioners: A Practical Introduction for Real-World Use

April 7, 2026
Published
#Cloud Automation#DevOps#Infrastructure as Code#Provisioners#Terraform

Most Terraform tutorials will tell you one thing pretty early: avoid provisioners if you can. And yet, they exist for a reason.

This creates a natural question—what exactly are Terraform provisioners, and when do they actually make sense? Let’s dig into that without the usual hand-waving.

What are Terraform Provisioners?

Provisioners are a way to run scripts or commands after a resource is created (or destroyed). Think of them as a bridge between infrastructure provisioning and configuration steps.

For example, Terraform can create a virtual machine, but it doesn’t natively install software on it. That’s where provisioners come in.

A quick example

Here’s a minimal example using the remote-exec provisioner:

TEXT
1resource "aws_instance" "web" {
2  ami           = "ami-0c55b159cbfafe1f0"
3  instance_type = "t2.micro"
4
5  provisioner "remote-exec" {
6    inline = [
7      "sudo apt update",
8      "sudo apt install -y nginx"
9    ]
10  }
11
12  connection {
13    type        = "ssh"
14    user        = "ubuntu"
15    private_key = file("~/.ssh/id_rsa")
16    host        = self.public_ip
17  }
18}

Terraform creates the instance, then SSHs into it and installs Nginx.

The Three Main Types of Provisioners

Terraform ships with a few built-in provisioners. Each serves a different purpose.

1. local-exec

Runs a command on the machine where Terraform is executed.

TEXT
1provisioner "local-exec" {
2  command = "echo Instance created with IP ${self.public_ip}"
3}

Useful for:

  • Logging
  • Triggering external scripts
  • Integrating with local tools

2. remote-exec

Executes commands on the created resource via SSH or WinRM.

This is the most commonly used provisioner for bootstrapping instances.

3. file

Copies files from your local machine to the resource.

TEXT
1provisioner "file" {
2  source      = "app.conf"
3  destination = "/etc/app.conf"
4}

Often used together with remote-exec for setup tasks.

Here’s Where Things Get Interesting

Provisioners feel powerful—but they come with trade-offs that aren’t obvious at first.

Terraform is declarative. Provisioners are not.

They introduce imperative behavior into a system designed for predictable state management.

Provisioners operate outside Terraform’s state model, which makes them harder to track, retry, or debug.

This is why experienced teams treat them as a last resort, not a default tool.

When Should You Use Terraform Provisioners?

Despite the warnings, there are valid use cases.

  • Bootstrapping a server when no image or configuration tool is available
  • Running one-off scripts after resource creation
  • Integrating with legacy systems that don’t support modern tooling
  • Quick prototypes where simplicity matters more than maintainability

A common real-world example: initializing a database after provisioning.

When You Should Avoid Them

This is where many teams get into trouble.

Avoid provisioners if you’re doing any of the following:

  • Installing complex application stacks
  • Managing ongoing configuration changes
  • Handling idempotent infrastructure updates

Instead, use tools designed for those tasks:

  • Configuration management: Ansible, Chef, Puppet
  • Image baking: Packer
  • Containerization: Docker + Kubernetes

A Better Pattern: Immutable Infrastructure

Rather than installing software after provisioning, a more robust approach is to bake everything into an image.

For example:

  • Use Packer to create an AMI with Nginx pre-installed
  • Use Terraform only to deploy that AMI

This eliminates the need for provisioners entirely and results in:

  • Faster deployments
  • More predictable environments
  • Fewer runtime surprises

Gotchas That Catch Developers Off Guard

Provisioners look simple, but there are a few sharp edges.

1. They don’t re-run automatically

If a provisioner fails or something changes later, Terraform won’t necessarily re-execute it unless the resource is recreated.

2. Debugging is limited

Errors often come from external systems (SSH, scripts), not Terraform itself.

3. Order matters

Provisioners run in the order they are defined, which can introduce hidden dependencies.

4. Sensitive data risks

Credentials passed in scripts may leak into logs if not handled carefully.

A Small but Useful Pattern

If you must use provisioners, keep them minimal and focused.

TEXT
1provisioner "remote-exec" {
2  inline = [
3    "test -f /etc/myapp/installed || sudo /usr/local/bin/install.sh"
4  ]
5}

This adds a basic idempotency check to prevent repeated execution issues.

So… Are Provisioners Bad?

Not exactly. They’re just easy to misuse.

Think of them as a temporary bridge:

  • Helpful in early stages
  • Useful for edge cases
  • Replaceable as your infrastructure matures

Many teams start with provisioners and gradually phase them out in favor of cleaner patterns.

Wrapping It Up

Terraform provisioners give you a way to execute scripts during resource creation, but they sit slightly outside Terraform’s core design philosophy.

Use them when you need a quick, direct solution—but be cautious about building long-term workflows around them.

If you remember one thing, make it this: provisioners solve problems—but often not in the most maintainable way.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: