Terraform by Example: Provisioners
Provisioners execute scripts during resource creation. This sample code shows `local-exec` and `remote-exec`, but remember they should be a last resort.
Code
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
key_name = "my-key"
# Execute command on the machine running Terraform
provisioner "local-exec" {
command = "echo ${self.private_ip} >> private_ips.txt"
}
# Execute command on the remote resource
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl start nginx"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/my-key.pem")
host = self.public_ip
}
}
}Explanation
Provisioners allow you to execute scripts on a local or remote machine as part of the resource creation or destruction process. The local-exec provisioner runs commands on the machine executing Terraform, while remote-exec connects to the resource (usually via SSH) to run commands. They are often used for bootstrapping or initial configuration.
However, HashiCorp explicitly recommends using provisioners only as a last resort. They introduce imperative logic into a declarative tool, break idempotency, and can be difficult to debug. If a provisioner fails, the resource is marked as "tainted" and must be recreated, which can lead to data loss or downtime.
Instead of provisioners, prefer using more robust alternatives:
- Packer: Create pre-configured machine images (AMIs)
- Cloud-init: Use standard initialization scripts (e.g., AWS User Data)
- Ansible/Chef: Use dedicated configuration management tools
Code Breakdown
local-exec runs on your computer, not the server.remote-exec runs commands on the newly created instance via SSH.inline passes a list of shell commands to execute.connection block defines how Terraform connects to the server (SSH/WinRM).private_key reads the SSH key file for authentication.host = self.public_ip uses the resource's own IP address for the connection.
