Introduction
When Terraform provisions infrastructure, it creates the resources defined in the configuration files, such as virtual machines or databases. However, a newly created resource is often in a default, unconfigured state. To make a server functional, it usually requires software installation, configuration file placement, or initial bootstrapping.
Terraform provisioners handle this specific stage of the deployment lifecycle. They act as a bridge between infrastructure provisioning and configuration management, allowing users to execute scripts or transfer files as part of the resource creation process. While best practices often recommend using image builders (like Packer) or user-data scripts for these tasks, provisioners provide a necessary solution for immediate, post-deployment actions.
Here is an overview of the three primary provisioners available in Terraform.
1. local-exec
The local-exec provisioner invokes a local executable or script on the machine running Terraform, not on the resource being created. This process runs on the device where the terraform apply command is executed, whether that is a developer's laptop or a CI/CD build server.
This provisioner is typically used for tasks that need to happen outside the infrastructure environment, such as updating local configuration files, saving output values to a disk, or triggering external automation tools like Ansible playbooks.
Example: In this example, the provisioner saves the public IP address of a newly created EC2 instance to a local text file immediately after the instance is provisioned.
resource "aws_instance" "web_server" {
ami = "ami-12345678"
instance_type = "t2.micro"
# Executes on the machine running Terraform
provisioner "local-exec" {
command = "echo 'Server IP: ${self.public_ip}' >> server_ips.txt"
}
}
2. remote-exec
The remote-exec provisioner executes commands directly on the remote resource being created. Unlike local-exec, this requires a network connection to the resource. For Linux servers, this is typically done via SSH, and for Windows servers, via WinRM.
This provisioner is essential for bootstrapping a server. It is commonly used to update package repositories, install necessary software packages, or start system services immediately after the operating system boots. Because it requires access to the machine, a connection block providing credentials (user and private key) is mandatory.
Example: This configuration logs into a new Ubuntu server and runs commands to install and start the Nginx web server.
resource "aws_instance" "web_server" {
# ... standard ec2 config ...
# Define connection details
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/my-keypair.pem")
host = self.public_ip
}
# Executes on the remote server
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl start nginx"
]
}
}
3. file
The file provisioner is used to copy files or directories from the machine running Terraform to the newly created remote resource. It serves as a simple transport mechanism for configuration management.
This is particularly useful for moving static application files, configuration settings (such as nginx.conf), or scripts that need to reside on the server. Like remote-exec, the file provisioner requires a valid connection block to establish a secure transfer channel.
Example: This snippet uploads a local configuration file named app.conf to the temporary directory of the remote server.
resource "aws_instance" "web_server" {
# ... standard ec2 config ...
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/my-keypair.pem")
host = self.public_ip
}
# Copies local file to remote destination
provisioner "file" {
source = "configs/app.conf" # Local path
destination = "/tmp/app.conf" # Remote path
}
}
Conclusion
Terraform provisioners provide essential functionality for the "last mile" of infrastructure deployment. They enable immediate interaction with resources through local scripting, remote command execution, and file transfers.
While they are powerful, they should be used judiciously. Since Terraform cannot track the state of changes made inside provisioners, complex configurations are often better handled by dedicated configuration management tools or pre-baked machine images. However, for straightforward bootstrapping and setup tasks, provisioners remain an effective and flexible tool in the Terraform ecosystem.
Top comments (0)