AWS with Terraform (Day 19)
Terraform Provisioners Explained: local-exec, remote-exec, and file
Provisioners in Terraform are often misunderstood. Many beginners either overuse them or avoid them entirely without understanding where they fit. In this post, I’ll explain what Terraform provisioners actually do, when they make sense, and how local-exec, remote-exec, and file provisioners behave in real-world scenarios, based on a hands-on EC2 demonstration.
This was part of my Day 19 learning, focused on understanding the imperative escape hatch Terraform provides when declarative resources alone are not enough.
What are Terraform Provisioners?
A provisioner allows Terraform to execute imperative actions during a resource’s lifecycle, usually at creation time or destruction time.
Provisioners are commonly used for:
-
Bootstrapping instances
-
Copying files to servers
-
Running scripts or commands that Terraform resources cannot express directly
Terraform documentation clearly states that provisioners should be used sparingly. Whenever possible, prefer:
-
Cloud-init / user data
-
Native Terraform resources
-
Immutable images (AMIs)
-
Configuration management tools
Provisioners are best treated as a last-mile tool, not a primary design pattern.
Provisioner Types in Terraform
Terraform supports three commonly used provisioners:
-
local-exec -
remote-exec -
file
Each one serves a different purpose.
local-exec Provisioner
The local-exec provisioner runs commands on the machine where Terraform itself is executed, not on the cloud resource.
When to use local-exec
-
Generating files locally
-
Calling external CLI tools
-
Logging or notifying systems during apply
-
Triggering scripts in CI/CD pipelines
Key point
local-exec does not require SSH, keys, or network access to the resource. It runs entirely on your local system or CI runner.
Because it runs locally, it is not suitable for server bootstrapping.
remote-exec Provisioner
The remote-exec provisioner runs commands on the remote resource itself, typically over SSH.
Common use cases
-
Installing packages
-
Creating files or directories
-
Running setup commands after instance creation
Requirements
To use remote-exec, you must define a connection block:
-
SSH user
-
Private key
-
Host (usually the instance public IP)
You must also ensure:
-
Security groups allow SSH
-
Private key permissions are correct
-
The instance is reachable when Terraform runs
Because remote-exec depends on SSH connectivity, it is more fragile than cloud-init and should be used carefully.
file Provisioner
The file provisioner copies files from the local machine to the remote instance over SSH.
Typical usage
-
Uploading scripts
-
Copying configuration files
-
Transferring certificates or assets
The file provisioner does not execute anything. It only copies files. In most cases, it is paired with a remote-exec provisioner to run the uploaded script.
Using the self Object
Inside a resource block, Terraform exposes a special object called self.
Useful attributes include:
-
self.id -
self.public_ip -
self.private_ip
These attributes are commonly used in provisioners and connection blocks to dynamically reference the resource being created.
Forcing Provisioners to Re-run
Terraform provisioners run only when a resource is created or recreated.
If the resource has no changes, provisioners will not execute again.
To force a re-run during testing:
This marks the resource for recreation, triggering all provisioners again.
Practical Observations
From hands-on testing, a few important lessons stand out:
-
local-execis excellent for automation around Terraform, not inside servers -
remote-execandfiledepend heavily on network stability and SSH correctness -
Provisioner failures can taint resources and cause unexpected recreations
-
Sensitive data should never be hardcoded inside provisioner commands
-
Provisioners increase coupling between infrastructure and configuration
Best Practices
-
Prefer cloud-init or user data for instance bootstrapping
-
Use provisioners only when declarative resources are insufficient
-
Keep provisioner logic simple and idempotent
-
Avoid long-running or complex scripts inside provisioners
-
Always destroy test resources to avoid unnecessary costs
Diagram
Final Thoughts
Terraform provisioners are powerful, but they are not the default solution. They exist to bridge the gap between declarative infrastructure and imperative configuration when no better option is available.
Here is the session link:
Comments
Post a Comment