Terraform Provisioners
Terraform provisioners are used to execute scripts or commands during resource creation or destruction. The three main types are
local-exec
remote-exec
file
Local-Exec Provisioner
The local-exec provisioner runs a command or script on the machine where Terraform is executed, not on the remote resource itself; that is, the local system.
Remote-Exec Provisioner
The remote-exec provisioner executes commands directly on the remote resource (such as an EC2 instance) via SSH. This is ideal for installing software, configuring the operating system, or running bootstrap scripts on the newly created resource.
File Provisioner
The file provisioner copies a file from the local machine to the remote resource. This is commonly used to transfer configuration files, scripts, or other assets to the remote system as part of the provisioning process. This also uses SSH for a secure connection.
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
The data "aws_ami" block finds the latest Ubuntu 22.04 AMI from the official Ubuntu provider that matches the specified filters. This ensures a recent, compatible image for the instance.
resource "aws_security_group" "ssh" {
name = "tf-prov-demo-ssh"
description = "Allow SSH inbound"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
The aws_security_group resource creates a security group that allows SSH access (port 22) from any IP address and permits all outbound traffic.
resource "aws_instance" "demo" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.ssh.id]
tags = {
Name = "terraform-provisioner-demo"
}
connection {
type = "ssh"
user = var.ssh_user
private_key = file(var.private_key_path)
host = self.public_ip
}
}
The aws_instance resource launches an EC2 instance using the Ubuntu AMI, with the instance type and SSH key defined in variables. It attaches the security group created earlier and tags the instance for identification. The connection block sets up SSH credentials, allowing Terraform to run provisioners or commands on the instance after it's created.
Creating the key pair
aws ec2 create-key-pair --key-name terraform-demo-key \
--query 'KeyMaterial' --output text > terraform-demo-key.pem
chmod 400 terraform-demo-key.pem
If the region is not specified, we can add the region with:
aws ec2 create-key-pair --key-name terraform-demo-key \
--region us-east-1 \
--query 'KeyMaterial' --output text > terraform-demo-key.pem
chmod 400 terraform-demo-key.pem
Terraform apply:
tf apply -var='key_name=terraform-demo-key' \
-var='private_key_path=./terraform-demo-key.pem'
Local Exec
First run terraform taint <instance>. To mark the instance as tainted for a recreation.
Adding the local execution inside the EC2 instance:
provisioner "local-exec" {
command = "echo 'Local-exec: created instance ${self.id} with IP ${self.public_ip}'"
}
Apply again:
tf apply -var='key_name=terraform-demo-key' \
-var='private_key_path=./terraform-demo-key.pem'
We will get the output:
aws_instance.demo: Provisioning with 'local-exec'...
aws_instance.demo (local-exec): Executing: ["/bin/sh" "-c" "echo 'Local-exec: created instance i-04f9653d8d76fdefb with IP 34.227.226.106'"]
aws_instance.demo (local-exec): Local-exec: created instance i-04f9653d8d76fdefb with IP 34.227.226.106
Remote Exec
Additionally, run the taint function before making changes and recreating the instance.
provisioner "remote-exec" {
connection {
type = "ssh"
user = var.ssh_user
private_key = file(var.private_key_path)
host = self.public_ip
timeout = "5m"
}
inline = [
"sudo apt-get update",
"echo 'Hello from remote-exec' | sudo tee /tmp/remote_exec.txt",
]
}
We can see the file created at the remote EC2 machine:
ubuntu@ip-172-31-26-119:/tmp$ ls -l
total 20
-rw-r--r-- 1 root root 23 Dec 16 04:14 remote_exec.txt
drwx------ 3 root root 4096 Dec 16 04:13 snap-private-tmp
drwx------ 3 root root 4096 Dec 16 04:13 systemd-private-eec7bbc10065448983d65bf0f99f0af1-chrony.service-Pf8o3B
drwx------ 3 root root 4096 Dec 16 04:13 systemd-private-eec7bbc10065448983d65bf0f99f0af1-systemd-logind.service-h5xdCo
drwx------ 3 root root 4096 Dec 16 04:13 systemd-private-eec7bbc10065448983d65bf0f99f0af1-systemd-resolved.service-FTqx1I
-rwxrwxrwx 1 ubuntu ubuntu 0 Dec 16 04:14 terraform_2098835113.sh
ubuntu@ip-172-31-26-119:/tmp$ cat remote_exec.txt
Hello from remote-exec
ubuntu@ip-172-31-26-119:/tmp$
File Exec
provisioner "file" {
source = "${path.module}/scripts/welcome.sh"
destination = "/tmp/welcome.sh"
}
provisioner "remote-exec" {
inline = [
"sudo chmod +x /tmp/welcome.sh",
"sudo /tmp/welcome.sh"
]
}
This should create a new file that we copied from our local machine to the remote machine.
ubuntu@ip-172-31-19-246:/tmp$ ls -lrt
total 28
drwx------ 3 root root 4096 Dec 16 04:25 systemd-private-70b6aad7485e45d182d09e768ed0650b-systemd-resolved.service-JH6NZE
drwx------ 3 root root 4096 Dec 16 04:25 systemd-private-70b6aad7485e45d182d09e768ed0650b-chrony.service-Q8bwgn
drwx------ 3 root root 4096 Dec 16 04:25 systemd-private-70b6aad7485e45d182d09e768ed0650b-systemd-logind.service-uqPiiF
drwx------ 3 root root 4096 Dec 16 04:25 snap-private-tmp
-rw-r--r-- 1 root root 23 Dec 16 04:25 remote_exec.txt
-rwxrwxrwx 1 ubuntu ubuntu 0 Dec 16 04:26 terraform_886993366.sh
-rwxr-xr-x 1 ubuntu ubuntu 234 Dec 16 04:26 welcome.sh
-rw-r--r-- 1 root root 153 Dec 16 04:26 welcome_msg.txt
-rwxrwxrwx 1 ubuntu ubuntu 0 Dec 16 04:26 terraform_802726099.sh
ubuntu@ip-172-31-19-246:/tmp$ cat welcome.sh
#!/bin/bash
# A simple script copied to the instance during the file+remote-exec demo
set -e
echo "Welcome to the Provisioner Demo" | sudo tee /tmp/welcome_msg.txt
uname -a | sudo tee -a /tmp/welcome_msg.txt
cat /tmp/welcome_msg.txt
Make sure to destroy the resources using:
terraform destroy -var='key_name=terraform-demo-key' -var='private_key_path=./terraform-demo-key.pem' -auto-approve
Video reference:
Arigato!