Terraform Provisioners: Best Practices for Infrastructure Management
Terraform provisioners are powerful tools that allow you to execute scripts or commands on remote or local machines during resource creation, update, or destruction. While they offer flexibility, their misuse can lead to complex, brittle, and hard-to-debug infrastructure. This module explores best practices to leverage provisioners effectively and avoid common pitfalls.
Understanding Provisioner Types
Terraform offers two primary provisioner types:
remote-exec
local-exec
Provisioner | Execution Location | Use Cases | Considerations |
---|---|---|---|
remote-exec | On the resource being created (e.g., a VM) | Bootstrapping software, configuring services, running application deployments | Requires network connectivity to the resource; can be complex to manage credentials and state. |
local-exec | On the machine running Terraform | Interacting with local tools, triggering CI/CD pipelines, generating configuration files | Less dependent on remote resource availability; can be useful for tasks not directly tied to the provisioned resource. |
When to Use Provisioners (and When Not To)
Provisioners are best used for tasks that are difficult or impossible to achieve declaratively within Terraform's resource model. This often includes initial bootstrapping or complex configuration steps.
Think of provisioners as a last resort for tasks that cannot be handled by declarative resource configurations or by using configuration management tools like Ansible, Chef, or Puppet.
Avoid using provisioners for tasks that can be managed declaratively. For example, installing packages or configuring services should ideally be handled by the machine's operating system or a dedicated configuration management tool. Over-reliance on provisioners can make your Terraform code harder to read, maintain, and debug.
Key Best Practices for Provisioners
To ensure your provisioner usage is robust and maintainable, adhere to these core principles:
1. Prefer Configuration Management Tools
Integrate with dedicated configuration management tools (Ansible, Chef, Puppet, SaltStack) whenever possible. These tools are designed for complex configuration tasks and offer better state management, idempotency, and error handling than raw scripts.
2. Keep Scripts Idempotent
Ensure your scripts can be run multiple times without unintended side effects. This is crucial because Terraform might re-run provisioners under certain conditions, and idempotency prevents breaking changes.
An idempotent script produces the same result regardless of how many times it is executed.
3. Use `when` and `on_failure` Appropriately
The
when
create
update
destroy
on_failure
continue
fail
on_failure = "fail"
4. Avoid Provisioners on `destroy` if Possible
Provisioners on destroy can be tricky. If a resource is already unhealthy or unreachable, a destroy provisioner might fail, potentially leaving resources in an inconsistent state. Consider using separate cleanup scripts or relying on the cloud provider's lifecycle hooks.
5. Manage Sensitive Data Securely
Never embed sensitive information (passwords, API keys) directly in provisioner scripts. Use environment variables, secrets management tools, or Terraform's sensitive variables feature.
6. Limit Provisioner Scope
Keep provisioner scripts focused on a single, well-defined task. Complex scripts are harder to debug and maintain. Break down large tasks into smaller, manageable scripts.
7. Use `local-exec` for Local Operations
For tasks that don't require execution on the remote resource,
local-exec
Example Scenario: Bootstrapping a Web Server
Consider bootstrapping a web server on an EC2 instance. Instead of a complex
remote-exec
A common pattern is to use remote-exec
to install an agent for a configuration management tool (like Ansible) or to download and run a more comprehensive bootstrap script. This separates the initial provisioning from the detailed configuration. For example, a remote-exec
provisioner might download an Ansible playbook and then execute it. The playbook itself would handle tasks like installing Nginx, configuring virtual hosts, and deploying application code, ensuring idempotency and better manageability.
Text-based content
Library pages focus on text content
Alternatively, cloud-init (user data) on AWS or cloud-config on other platforms can be used to perform initial setup tasks like installing packages and starting services upon instance launch, often a more robust approach than
remote-exec
Provisioner Lifecycle and Dependencies
Terraform executes provisioners after a resource has been created but before it's considered fully created. Provisioners are also subject to resource dependencies. If a provisioner depends on another resource, Terraform will ensure that dependent resource is created first.
Loading diagram...
Understanding this lifecycle helps in debugging and ensuring that the environment is ready for the provisioner's execution.
Conclusion
Terraform provisioners are a powerful but potentially complex feature. By adhering to best practices, such as preferring configuration management tools, ensuring idempotency, and limiting their scope, you can effectively use provisioners to bootstrap and configure your infrastructure without introducing unnecessary fragility.
Learning Resources
The official HashiCorp documentation on Terraform provisioners, covering syntax, types, and lifecycle.
A blog post from HashiCorp discussing the purpose and best practices for using provisioners.
An article detailing the use cases and potential pitfalls of Terraform provisioners.
Guidance on how to effectively integrate Terraform with Ansible for robust infrastructure provisioning.
A deep dive into the `local-exec` provisioner, its use cases, and best practices.
An explanation of the `remote-exec` provisioner, its capabilities, and how to use it safely.
Explains the concept of idempotency, which is critical for understanding provisioner best practices.
A tutorial offering practical advice and examples for using Terraform provisioners effectively.
AWS documentation on user data (cloud-init), a common alternative to `remote-exec` for initial bootstrapping.
A video tutorial that explores Terraform provisioners in detail, including practical examples and common mistakes.