Implementing Provisioners
1. Using Local-Exec Provisioner
| Attribute | Values | Description |
|---|---|---|
| command | string |
Required. The command to execute. Must be a string or a list of strings. |
| interpreter | list(string) |
Optional. A list of strings to use as the interpreter for the command. |
| environment | map(string) |
Optional. A map of environment variables to set during execution. |
| lifecycle | object |
Optional. Controls when the provisioner runs (e.g., create_before_destroy). |
Example: Running a local command
resource "null_resource" "local_exec_example" {
provisioner "local-exec" {
command = "echo 'Hello from local exec'"
}
}
Example: Setting environment variables
resource "null_resource" "env_example" {
provisioner "local-exec" {
command = "echo $MY_VAR"
environment = {
MY_VAR = "Hello World"
}
}
}
Note: The
local-exec provisioner runs on the machine where Terraform is executed, not on the target resource.
2. Using Remote-Exec Provisioner
| Property | Description | Values |
|---|---|---|
| connection | Connection configuration for remote execution | object |
| command | Command to execute on remote machine | string |
| inline | Inline script to execute | list(string) |
| interpreter | Interpreter to use for inline scripts | string |
| environment | Environment variables to set for the command | map(string) |
Example: Execute a script remotely
resource "null_resource" "remote_exec" {
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
host = "${aws_instance.example.public_ip}"
private_key = "${file("~/.ssh/id_rsa")}"
}
inline = [
"echo 'Hello, World!' > /tmp/hello.txt",
"chmod +x /tmp/hello.txt"
]
}
}
Note: The
remote-exec provisioner is useful for running commands on a remote machine after it has been created. It supports various connection types like SSH and WinRM.
Warning: Remote-exec provisioners are not idempotent and can cause issues if Terraform state is modified or the resource is recreated. Use with caution in production environments.
3. Using File Provisioner
| Property | Description | Example |
|---|---|---|
| source | Path to the local file or directory to copy | ./files/config.json |
| destination | Path on the remote instance where the file will be copied | /etc/app/config.json |
| content | Direct content to write to the file (alternative to source) | <?xml version="1.0"?><config><port>8080</port></config> |
Example: Copying a file to remote instance
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
provisioner "file" {
source = "./app-config.json"
destination = "/etc/app/config.json"
}
}
Example: Writing content directly to a file
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
provisioner "file" {
content = <<-EOF
<?xml version="1.0"?>
<config>
<port>8080</port>
<host>localhost</host>
</config>
EOF
destination = "/etc/app/config.xml"
}
}
Note: The file provisioner is executed during resource creation and destruction. It does not support
lifecycle rules like ignore_changes.
4. Configuring Connection Blocks
| Property | Description | Default | Example |
|---|---|---|---|
| type | Connection type (e.g., ssh, winrm) | ssh | type = "ssh" |
| host | Host address to connect to | None | host = "192.168.1.10" |
| user | Username for connection | root | user = "admin" |
| port | Port to connect on | 22 (SSH) | port = 2222 |
| private_key | Path to private key file | None | private_key = "/path/to/key" |
| password | Password for authentication | None | password = "secret" |
| timeout | Timeout for connection in minutes | 5m | timeout = "10m" |
Example: SSH Connection Configuration
connection {
type = "ssh"
host = "${aws_instance.example.private_ip}"
user = "ubuntu"
port = 22
private_key = "<%= File.read("~/.ssh/id_rsa") %>"
timeout = "10m"
}
Note: The
connection block is used to configure how Terraform connects to the provisioned resource. It's typically used in conjunction with provisioner blocks.
Warning: Avoid storing sensitive credentials like passwords directly in Terraform configuration files. Use environment variables or secrets management tools.
5. Setting Provisioner Timing
| Property | Description | Values | Notes |
|---|---|---|---|
| when | Determines when the provisioner runs | create, destroy |
Defaults to create. Set to destroy to run during resource destruction. |
| on_failure | Controls behavior if provisioner fails | continue, fail |
Defaults to fail. Set to continue to allow resource creation to proceed despite failure. |
Example: Setting provisioner timing
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo 'Instance created'"
when = "create"
}
provisioner "local-exec" {
command = "echo 'Instance destroyed'"
when = "destroy"
}
}
6. Handling Provisioner Failures
| Failure Type | Description | Behavior | Solution |
|---|---|---|---|
| provisioner failure | When a provisioner fails during execution | Stops further provisioning and marks resource as failed | Use on_failure = "continue" to proceed with other provisioners |
| timeout | Provisioner exceeds configured timeout | Interrupts and fails the operation | Adjust timeout attribute or use retry logic |
| script error | Script returns non-zero exit code | Halts execution and logs error | Ensure scripts are idempotent and handle errors gracefully |
Example: Using on_failure to continue on error
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo 'First provisioner'"
}
provisioner "local-exec" {
command = "exit 1"
on_failure = "continue"
}
provisioner "local-exec" {
command = "echo 'Second provisioner'"
}
}
Note: Setting
on_failure = "continue" allows other provisioners to run even if one fails, but it does not prevent the overall resource creation from failing.
Warning: Using
on_failure = "continue" can mask critical errors, leading to inconsistent state or partial configuration.
Best Practices for Provisioner Failures
- Use
on_failure = "continue"only when appropriate - Implement proper logging and monitoring for provisioner errors
- Ensure provisioners are idempotent to avoid issues on retries
- Validate scripts and commands before deploying in production
7. Using Null Resource
The null_resource is a special resource in Terraform that has no provider and does not manage any infrastructure. It's used to execute provisioners when other resources don't support them or for triggering actions after resource creation.
| Property | Description | Example |
|---|---|---|
| triggers | Map of values that, when changed, will cause the null resource to be recreated. | |
| provisioners | Configuration block to define provisioners (e.g., local-exec, remote-exec). | |
Example: Using null_resource with local-exec provisioner
resource "null_resource" "example" {
triggers = {
instance_id = aws_instance.example.id
}
provisioner "local-exec" {
command = "echo 'Instance ID is ${self.triggers.instance_id}'"
}
}
Note: The
null_resource is not intended for managing infrastructure but rather for executing actions or triggering workflows.
Warning: Provisioners should be used sparingly as they can make state management more complex and reduce the idempotency of your configurations.
8. Using terraform_data Resource
The terraform_data resource is a special-purpose resource in Terraform designed to manage data that is not directly tied to infrastructure but needs to be tracked and managed by Terraform.
| Attribute | Description | Required? |
|---|---|---|
| count | Number of instances to create. | No |
| for_each | Map or set of values to iterate over. | No |
| lifecycle | Customizes resource lifecycle behavior. | No |
| triggers | Map of values that, when changed, trigger a refresh or recreation. | No |
Example: Using terraform_data to trigger a refresh
resource "terraform_data" "example" {
triggers = {
timestamp = timestamp()
}
}
Example: Using terraform_data to manage a file
resource "terraform_data" "file_content" {
content = "Hello, World!"
file_name = "output.txt"
}
Note: The
terraform_data resource is not intended for managing infrastructure. It's used primarily for data management and triggering actions.
9. Using Time Provider
| Property | Description | Values | Notes |
|---|---|---|---|
| trigger | Triggers the time provider to update | string |
Used to force a refresh of the time |
| offset | Offset in seconds to add to the current time | number |
Can be positive or negative |
Example: Using Time Provider
resource "time_static" "example" {
triggers = {
timestamp = "${timestamp()}"
}
}
resource "time_offset" "example" {
offset = 3600
triggers = {
timestamp = "${timestamp()}"
}
}
10. Understanding Provisioner Limitations
| Limitation | Description | Impact |
|---|---|---|
| State Management | Provisioners run during apply and can't be easily re-executed without re-creating resources | Changes to provisioner logic may not be reflected in state, leading to inconsistent resource states |
| Idempotency | Provisioners are not guaranteed to be idempotent | Re-running provisioners can cause unintended side effects or errors if they modify external systems |
| Error Handling | Errors in provisioners halt the entire apply process | Resource creation or update fails entirely if any provisioner encounters an error |
| Security | Provisioners can execute arbitrary commands or scripts | Potential security risks if sensitive data (like passwords) are exposed in logs or configuration |
| Scalability | Provisioners run sequentially per resource | Can lead to long apply times in large-scale deployments where many resources require provisioners |
Note: Provisioners should be used sparingly and only when necessary, as they reduce the declarative nature of Terraform.