Considerations with Provisioners in Terraform are critical for anyone using Terraform to automate infrastructure. While provisioners provide flexibility to run scripts and commands during resource creation or destruction, their use should be handled with caution.
This blog explores when to use provisioners, best practices, and smarter alternatives that are better aligned with Terraform’s declarative nature. Whether you’re starting out or managing complex Terraform environments, understanding these considerations can help you write cleaner, safer, and more maintainable code.
Table of Contents
Why Provisioners Aren’t Always Ideal
Provisioners let you execute commands on local or remote systems after Terraform creates or destroys a resource. However, their behavior doesn’t always align well with Terraform’s model of infrastructure as code.
One of the biggest challenges with provisioners is predictability. Since they allow arbitrary commands, Terraform can’t fully understand or plan their outcomes. This can lead to inconsistencies between the execution plan and what’s actually applied.
Additionally, dependency on external factors like network connectivity or SSH authentication introduces potential points of failure. Remote provisioners, in particular, need a working connection block and access credentials—things that aren’t always available during deployment.
Hidden Complexity and Troubleshooting
1. Hard to Debug
Provisioners increase configuration complexity. When something fails, it’s often unclear whether the issue lies in the script, the connection, or the infrastructure itself. Terraform also doesn’t track what provisioners do, making debugging more difficult.
2. Not Reproducible
Since Terraform doesn’t track the exact effect of commands run via provisioners, repeating the same apply
may lead to different outcomes, especially when the scripts involve conditional logic or depend on external resources.
Native Alternatives to Provisioners
Instead of relying on provisioners, it’s often better to use built-in resource features from cloud providers.
For example, AWS EC2 instances support the user_data
argument. This allows you to run initialization scripts natively on the instance during boot without requiring a remote connection. This method is:
- Easier to maintain
- More secure (no SSH setup required)
- Fully declarative and compatible with Terraform’s planning model
Other cloud platforms offer similar native options:
- Azure:
custom_data
for virtual machines - Google Cloud:
metadata_startup_script
for compute instances
These options allow you to automate configuration in a more predictable and Terraform-friendly way.
Using Prebuilt Images Instead
A more advanced alternative is to build custom machine images that already include required software, configuration, and setup. This eliminates the need for any provisioning at launch time.
For example, instead of installing NGINX using a provisioner after launching an EC2 instance, you can:
- Create a custom AMI with NGINX pre-installed
- Use tools like Packer to automate this image creation
- Reference the custom AMI directly in your Terraform config
Benefits include:
- Faster instance boot times
- Fewer moving parts
- Better reproducibility
Custom images align well with Terraform’s declarative model and are a best practice in production environments.
When You Might Still Need Provisioners
Provisioners can still be useful, especially in the following cases:
- Ad-hoc scripts for quick testing or prototyping
- Final configuration tweaks when no native alternatives exist
- Resource cleanup before destroying infrastructure (using
when = "destroy"
)
However, even in these cases, ensure provisioners are:
- Simple and idempotent
- Properly logged
- Not critical to infrastructure correctness
Always set on_failure = "continue"
if you don’t want the apply step to break on script failure.
Summary: Best Practices for Provisioners
Practice | Recommendation |
---|---|
Use provisioners sparingly | ✅ Yes, only if no native alternative exists |
Prefer cloud-native mechanisms | ✅ Use user_data , metadata , etc. |
Avoid complex provisioning logic | ✅ Keep it minimal and idempotent |
Build custom machine images | ✅ Use Packer or other image tools |
Handle failures explicitly | ✅ Use on_failure = "continue" when needed |
Keeping provisioner usage minimal leads to more reliable, scalable, and readable Terraform code.
Conclusion
Considerations with Provisioners in Terraform are often overlooked but critical for writing efficient infrastructure code. While it’s tempting to use provisioners to run scripts and commands, doing so can undermine the predictability and clarity that Terraform provides.
Instead, lean on cloud-native features, custom images, or external automation tools. This shift not only simplifies your Terraform configuration but also aligns better with infrastructure as code best practices.
Use provisioners only as a last resort—and always with awareness of their limitations.
Frequently Asked Questions (FAQ)
1. Should I use provisioners in every Terraform deployment?
No, provisioners should be avoided unless absolutely necessary. Use native features like user_data
or custom AMIs instead.
2. What are the risks of using provisioners?
Provisioners introduce unpredictability, make debugging harder, and can fail due to SSH or network issues. They also aren’t tracked in Terraform’s state.
3. Are there alternatives to provisioners in Terraform?
Yes, most cloud providers offer native options like user_data
, metadata
, or custom_data
. Tools like Packer can also build preconfigured images.
4. How do I prevent provisioner failure from breaking apply?
Use on_failure = "continue"
in the provisioner block. This tells Terraform to proceed even if the script fails.
5. When is using a destroy-time provisioner appropriate?
Use it when you need to clean up or run final tasks before deleting a resource. For example, de-registering a server from a load balancer.