Skip to main content

Command Palette

Search for a command to run...

Terraform Provisioners

Published
4 min read

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!

More from this blog

Code Companions

32 posts