when = "destroy". Provisioners are commonly used to bootstrap instances, run initial configuration steps, or integrate with external systems when provider-native features or user-data/cloud-init are insufficient.
This lesson covers:
- Where provisioners fit into the Terraform lifecycle and the events that trigger them.
- Remote provisioners (
remote-exec) that run commands on provisioned infrastructure (for example, using SSH or WinRM via the resource’sconnectionsettings). - Local provisioners (
local-exec) that run on the machine executing Terraform—useful for orchestration or invoking external tooling. - Key considerations and risks such as idempotency, dependency handling, error handling, and why provisioners are generally a last-resort approach.

Prefer provider-native arguments, user-data/cloud-init, or configuration management tools whenever possible. Provisioners are intended as a pragmatic fallback when those options are not available.
Provisioners can introduce ordering and retry complexities, and may make deployments non-idempotent. Treat them as a last resort and ensure robust error handling and retries if you must use them.
What are provisioners?
Provisioners are Terraform blocks that run scripts or commands in relation to a resource. They are not part of the resource’s provider semantics and therefore can cause operational complexity if overused. Typical use cases include:- Running one-off bootstrap commands on newly-created compute instances.
- Copying files to instances after creation.
- Triggering external orchestration or deployment scripts that cannot be implemented via providers.
remote-exec— executes commands on the target machine via SSH or WinRM.file— copies files to the target machine.local-exec— executes commands on the Terraform host machine.
Lifecycle and trigger points
Provisioners are tied to resource lifecycle events. The common lifecycle triggers are:create(default): Runs after a resource is created.destroy(optional): Runs when the resource is being destroyed if you setwhen = "destroy".
Remote provisioner example (remote-exec)
A typicalremote-exec provisioner requires a connection block to reach the provisioned instance. Example (SSH-based bootstrap):
- Always prefer provider/user-data/user-data cloud-init for initial configuration where possible.
- Connection reliability and network reachability are common causes of failure for
remote-exec.
Local provisioner example (local-exec)
local-exec runs commands on the machine running Terraform. Use it for tasks like invoking CI/CD, notifying external systems, or manipulating local artifacts:
Remote vs Local: quick comparison
Key considerations and risks
When using provisioners, be mindful of:- Idempotency: Provisioner scripts should be idempotent to survive retries or partial failures.
- Ordering: Terraform’s graph determines ordering. Use explicit
depends_onwhen needed. - Error handling and retries: Provisioners can fail due to transient issues (network, SSH availability). Implement retries inside scripts and make Terraform handle failure paths.
- State drift: Provisioners may cause changes that are not reflected in Terraform state; prefer declarative approaches when possible.
- Security: Avoid embedding secrets directly in provisioners. Use secure secret solutions (e.g., vaults, cloud provider secret stores).
- Testing: Provisioner logic should be tested independently of Terraform to reduce deployment surprises.
Best practices and alternatives
Prefer these alternatives before reaching for provisioners:- User-data / cloud-init for OS-level bootstrapping.
- Provider-native configuration options or post-create APIs exposed by the provider.
- Configuration management tools (Ansible, Chef, Puppet) or immutable images (bake configuration into AMIs/VM images).
- Orchestration frameworks (Terraform + Packer) to pre-bake artifacts.
- Keep scripts small and idempotent.
- Use
fileto copy scripts andremote-execto execute them. - Use
when = "destroy"sparingly and only for meaningful cleanup tasks. - Use
depends_onto express explicit ordering where required. - Add retries and exponential backoff in scripts to handle transient failures.
Resources and further reading
- Terraform Provisioners Documentation
- Terraform Best Practices
- Cloud-init documentation
- Ansible — Configuration Management