Terraform’s remote-exec provisioner is your secret weapon for running commands on newly provisioned infrastructure, but it’s a bit of a black box if you don’t know how it works.
Here’s a fresh instance being provisioned, and right after terraform apply finishes, we’re going to run a script on it.
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
provisioner "remote-exec" {
inline = [
"sudo yum update -y",
"sudo yum install -y nginx",
"sudo systemctl start nginx",
"sudo systemctl enable nginx"
]
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/my-aws-key.pem") # Make sure this path is correct
host = self.public_ip
}
}
}
This block tells Terraform: "After you create this aws_instance, connect to it using SSH. Once connected, run these commands one after another." The connection block is crucial; it defines how Terraform should establish that SSH link. self.public_ip dynamically pulls the public IP address of the instance just created.
The core problem remote-exec solves is bootstrapping new machines. Imagine you’ve just launched a web server. You need to install Nginx, configure it, maybe pull down your application code. Doing this manually for every new instance is a pain. remote-exec automates that initial setup. It’s like having a tiny, automated sysadmin that runs on your new servers the moment they’re ready.
Internally, Terraform spins up the instance, waits for it to be reachable (usually via SSH port 22), then uses your specified SSH credentials to log in and execute the commands. It’s not magic; it’s just a sophisticated SSH client managed by Terraform.
The inline argument is great for quick, simple tasks. For more complex scripts, you’d use the script argument, pointing to a local file:
provisioner "remote-exec" {
script = "${path.module}/setup_webserver.sh"
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/my-aws-key.pem")
host = self.public_ip
}
}
The setup_webserver.sh file would contain all your installation and configuration commands.
The mental model is simple: Terraform creates, then Terraform connects and runs. The connection block is where you define the connection parameters, and the inline or script arguments are where you define the actions. It’s declarative for the infrastructure, and imperative for the post-creation setup.
What most people don’t realize is that remote-exec relies on the instance being SSH-accessible before it attempts to connect. If your security group is too restrictive, or if the instance takes a long time to boot and become SSH-ready, the provisioner will time out and fail. Terraform doesn’t magically bypass firewalls; it uses standard SSH.
The next hurdle you’ll likely face is managing sensitive data like API keys or passwords that need to be passed into your remote-exec scripts.