Learn how to execute multiple commands on your remote instances using Terraform's remote-exec provisioner with practical examples and workarounds.
Terraform's remote-exec
provisioner is a powerful tool for running commands on your resources, but it's essential to understand its limitations. A key point is that remote-exec
runs only once during a resource's creation. This means if you modify the commands within remote-exec
after the resource is already up and running, Terraform won't automatically re-execute those commands on the existing resource.
Terraform's remote-exec
provisioner runs only once when a resource is created. If you modify the commands within remote-exec
after the resource exists, Terraform won't re-run them automatically.
Let's say you have this initial setup:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c9"
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = ["echo 'Initial setup' > /tmp/setup.txt"]
}
}
If you later change the inline
command to:
inline = ["echo 'Updated setup' > /tmp/setup.txt"]
Terraform won't update /tmp/setup.txt
on the existing instance.
Here's why and how to handle such situations:
Idempotency: Terraform strives to be idempotent. It aims to bring your infrastructure to the desired state without repeating actions unnecessarily. Since the resource already exists, Terraform assumes the initial provisioning was successful and doesn't re-run it.
Resource Lifecycle: Provisioners are tightly coupled to the lifecycle of a resource. They primarily run during the creation phase.
Solutions:
Resource Recreation: The most straightforward approach is to:
terraform destroy
targeting the specific resource).terraform apply
) which will recreate the resource and execute the updated remote-exec
commands.null_resource
with Triggers: For more granular control, use the null_resource
in conjunction with triggers:
resource "null_resource" "setup" {
triggers = {
commands = filemd5("/path/to/your/setup/script.sh")
}
provisioner "remote-exec" {
inline = ["bash /path/to/your/setup/script.sh"]
}
}
triggers
block ensures the remote-exec
runs whenever the content of your script changes. The filemd5
function calculates the MD5 checksum of the script, triggering the null_resource
to re-provision when the checksum changes.Important Considerations:
This code example illustrates the problem of Terraform's remote-exec
not executing again when resources are updated. It presents two solutions: recreating the resource and using a null_resource
. The first solution involves deleting and recreating the resource to trigger the remote-exec
provisioner. The second solution utilizes a null_resource
with a remote-exec
provisioner and a trigger based on the script's hash. Modifying the script triggers the null_resource
to re-run the remote-exec
. The choice between these solutions depends on the specific requirements and trade-offs of the situation.
This example demonstrates the issue of remote-exec
not re-running on resource updates and provides solutions using resource recreation and null_resource
.
Initial Setup (main.tf):
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "us-west-2" # Replace with your desired region
}
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c9" # Replace with a suitable AMI ID
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = ["echo 'Initial setup' > /tmp/setup.txt"]
}
}
1. Resource Recreation:
terraform apply
to create the initial instance.inline
command in aws_instance.example
to:
inline = ["echo 'Updated setup' > /tmp/setup.txt"]
terraform apply
. You'll notice Terraform doesn't update the file.terraform destroy
(or target the specific instance).terraform apply
again. This recreates the instance, and the updated command runs.2. Using null_resource
(main.tf):
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c9" # Replace with a suitable AMI ID
instance_type = "t2.micro"
}
resource "null_resource" "setup" {
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu" # Replace with your instance user
private_key = file("~/.ssh/your-key.pem") # Replace with your private key path
host = aws_instance.example.public_ip
}
inline = ["bash /tmp/setup.sh"]
}
triggers = {
script_hash = filemd5("/tmp/setup.sh") # Replace with your script path
}
}
setup.sh
:
#!/bin/bash
echo "Updated setup from script" > /tmp/setup.txt
terraform apply
.setup.sh
.terraform apply
again. The null_resource
detects the script change and re-runs the remote-exec
.Remember:
remote-exec
connection. Adjust accordingly if you're using a different method.null_resource
approach provides more control but adds complexity. Choose the solution that best suits your needs.This document explains that Terraform's remote-exec
provisioner is not intended for ongoing configuration management. It runs only once when a resource is created, and subsequent changes to the remote-exec
block won't be applied automatically.
Here are some additional points to consider:
Why this behavior is desirable:
When to use remote-exec
:
When to avoid remote-exec
:
remote-exec
. A mistake during provisioning could lead to data loss or service disruption.Alternatives to resource recreation:
remote-exec
for gathering data.Best Practices:
terraform plan
, terraform validate
) to catch potential issues before applying changes.By understanding the limitations and appropriate use cases of remote-exec
, you can leverage Terraform effectively while maintaining a predictable and manageable infrastructure.
Problem: Terraform's remote-exec
provisioner runs only once during resource creation. Modifying remote-exec
commands after resource creation won't trigger re-execution, leaving your infrastructure in a potentially outdated state.
Why?
Solutions:
Resource Recreation:
terraform destroy
to remove the existing resource.terraform apply
, triggering remote-exec
on the newly created resource.null_resource
with Triggers:
null_resource
for more controlled re-provisioning.triggers
based on file changes (e.g., using filemd5
) to automatically trigger remote-exec
when your scripts are modified.resource "null_resource" "setup" {
triggers = {
commands = filemd5("/path/to/your/setup/script.sh")
}
provisioner "remote-exec" {
inline = ["bash /path/to/your/setup/script.sh"]
}
}
Important Considerations:
Terraform's remote-exec
provisioner, while useful for initial resource setup, has limitations due to its one-time execution nature. It runs only during resource creation and modifications to the remote-exec
block won't apply to existing resources. This behavior stems from Terraform's emphasis on idempotency and predictable infrastructure management. For simple, one-time configurations like initial package installations or script executions, remote-exec
proves beneficial. However, for complex, ongoing configuration management, dedicated tools like Ansible, Chef, or Puppet are recommended. When updates are needed, consider resource recreation by deleting and re-creating the resource, which triggers remote-exec
on the new instance. Alternatively, for more granular control, leverage the null_resource
with triggers based on file changes, ensuring remote-exec
runs when your scripts are modified. Remember to manage your Terraform state file diligently and explore alternative approaches like data sources when fetching information from resources post-creation. By understanding these nuances and best practices, you can effectively utilize Terraform's remote-exec
provisioner while maintaining a predictable and manageable infrastructure.