Terraform Provisioners offer a flexible method to execute scripts and commands during your infrastructure deployment lifecycle. Whether you’re automating configuration steps after resource creation or capturing data locally, provisioners can enhance your Terraform workflows when used wisely.
This comprehensive guide walks you through using provisioners in Terraform—what they are, how they work, and when to use them (or avoid them). It’s structured for everyone from beginners to experienced DevOps engineers.
Table of Contents
What Are Terraform Provisioners?
Terraform Provisioners are tools that allow you to execute scripts or commands at specific stages of your infrastructure provisioning. They can operate on the resource itself (remote) or on the machine running Terraform (local).
Provisioners typically execute at two stages:
- Create-time: After a resource is provisioned.
- Destroy-time: Before a resource is deleted.
While provisioners can be powerful, Terraform recommends using them only when native provider features don’t suffice.
Using Remote Exec Provisioners
Executing Scripts on Remote Instances
The remote-exec
provisioner lets you run commands on a virtual machine or server after Terraform creates it.
Here’s how it works:
- Defined inside a resource block like
aws_instance
. - Uses
inline
scripts to define the actions to run.
Example:
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo apt install nginx -y",
"sudo systemctl enable nginx",
"sudo systemctl start nginx"
]
}
These commands configure an Ubuntu EC2 instance to run an NGINX web server automatically.
SSH Connection Setup
To run these remote commands, Terraform needs access to the target machine. This is done using the connection
block:
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = file("~/.ssh/web.pem")
}
This configuration uses SSH to connect to the server using the instance’s public IP and a private key. Without this, the remote-exec
provisioner won’t work.
Using Local Exec Provisioners
Running Commands on Your Local Machine
The local-exec
provisioner executes commands on the machine where you’re running Terraform—not the cloud resource.
Use cases include:
- Logging information locally.
- Sending notifications after deployments.
- Running CLI tools post-apply.
Example:
provisioner "local-exec" {
command = "echo ${self.public_ip} > ~/instance_ip.txt"
}
This stores the instance’s public IP in a local file—useful for connecting later or logging.
Handling Provisioner Failures Gracefully
The on_failure
Argument
Provisioners fail when their command exits with a non-zero status. By default, this will fail the entire terraform apply
process and mark the resource as tainted.
To override this behavior, use:
on_failure = "continue"
This setting tells Terraform to continue deployment even if the provisioner fails.
Destroy-Time Provisioners
Cleaning Up Before Resource Deletion
Terraform also supports destroy-time provisioners. These execute just before a resource is deleted, allowing for cleanup tasks.
Example:
provisioner "local-exec" {
when = "destroy"
command = "echo Deleting ${self.public_ip} >> ~/destroy_log.txt"
}
These are helpful when you need to notify other systems or archive data before tearing down infrastructure.
Best Practices for Using Terraform Provisioners
While provisioners can be incredibly useful, here are some guidelines:
- Use provider-native methods like
user_data
for AWS EC2 orcustom_data
in Azure when possible. - Avoid complex logic in provisioners. Use configuration management tools (e.g., Ansible, Chef) for advanced automation.
- Isolate connection-sensitive logic and always secure your SSH or WINRM credentials.
- Test incrementally, especially when adding
remote-exec
ordestroy
provisioners.
Over-relying on provisioners may lead to less predictable, harder-to-debug Terraform code.
Conclusion
Using Terraform Provisioners gives you the power to automate tasks before and after your infrastructure is deployed. From running shell scripts on remote servers to saving output locally, they add a layer of flexibility that can streamline your workflows—when used thoughtfully.
However, always prioritize using provider-specific attributes for configuration tasks. Treat provisioners as a helpful fallback, not the default method for setup.
By understanding how and when to use remote-exec
, local-exec
, and other provisioner options, you’ll create Terraform code that’s both powerful and maintainable.
Frequently Asked Questions (FAQs)
1. Should I always use provisioners in Terraform?
No. Use provisioners only when native provider features don’t support your use case. Prefer user_data
or metadata
where available.
2. What’s the difference between local and remote exec?
local-exec
runs on your machine, while remote-exec
runs on the created resource (like a cloud VM) after deployment.
3. Can I use provisioners during destroy operations?
Yes. Use the when = "destroy"
option inside the provisioner block to define a destroy-time action.
4. What happens if a provisioner fails?
By default, the resource is marked as tainted, and the deployment fails. You can change this with on_failure = "continue"
.
5. Are provisioners secure?
They can expose secrets or credentials if not handled carefully. Use secure key handling practices and restrict access as needed.