This article explains how to effectively use Terraform provisioners for remote and local operations during resource management.
In this article, we revisit Terraform provisioners and demonstrate how to use them effectively for both remote and local operations. Provisioners allow you to execute commands or scripts on remote resources or on the local machine where Terraform is run. For example, the remote-exec provisioner can be used to execute a bash script immediately after a resource is created.
Below is an example using our familiar web server scenario. In this configuration, an inline script is executed after an EC2 instance is deployed. This script updates the package index, installs NGINX, and then enables and starts the service:
Ensure that the proper network connectivity (SSH for Linux or WinRM for Windows), security groups, and an SSH key pair are in place for successful execution.
This example expands on the previous one by including a connection block, which specifies the SSH details required to connect to the remote instance. The connection block leverages the self.public_ip expression to dynamically reference the deployed instance’s public IP address:
Provisioners are not limited to remote tasks. The local-exec provisioner is used to execute commands on the local machine where Terraform is run. This is particularly useful for gathering information and saving it locally. The following example stores the public IP address of an EC2 instance in the /tmp/ips.txt file:
By default, provisioners run after a resource is created (create-time provisioners). However, you can also configure a provisioner to run before a resource is destroyed (destroy-time provisioners) using the when argument. The example below demonstrates both scenarios:
A key behavior of provisioners is that if the command or script execution fails, the entire terraform apply operation will error out. For example, specifying an incorrect file path can lead to failure:
$ terraform applyError: Error running command 'echo 35.183.14.192 > /temp/pub_ip.txt': exit status 1.Output: The system cannot find the path specified.
To modify this behavior, you can set the on_failure argument within the provisioner block. This allows Terraform to continue with resource creation even if the command fails. For example:
Best Practices: Using Native Resource Capabilities
Terraform recommends using provisioners only as a last resort. Whenever possible, prefer native resource configurations supplied by the provider. For example, AWS EC2 instances can be configured using user data, Azure Virtual Machines with custom data, or metadata for GCP. The following example leverages AWS EC2 user data to install and start NGINX on an Ubuntu instance without resorting to provisioners:
Terraform provisioners, such as remote-exec and local-exec, provide flexibility for post-deployment configuration and local operations. However, they should be used judiciously. Leveraging native resource capabilities results in more reliable and maintainable configurations.