Terraform certificate #3

Terraform certificate #3

Source: Dev.to

Terraform Provisioners ## 1. What problem do provisioners solve? ## 2. What is a provisioner? ## 3. Important exam & production note ## Real-world ## 4. How provisioners work (execution flow) ## Phase 1 – Create resource ## Phase 2 – Provision ## 5. Types of provisioners (high level) ## 6. Real, minimal working example (NGINX on EC2) ## 6.1 Requirements ## 6.2 Terraform code (single file) ## 7. What happens when you run Terraform? ## Step 1 ## Step 2 ## Terraform output flow ## 8. How Terraform connects to EC2 ## 9. Verifying the result ## 10. Why provisioners are controversial ## 11. When provisioners ARE acceptable ## 12. One-sentence summary for students ## Terraform Provisioners — Types & Definition Format ## 1. What are provisioners? ## 2. Types of provisioners in Terraform ## Important note ## 3. High-level difference (very important) ## local-exec ## remote-exec ## 4. Rule #1 — Where provisioners are defined ## 5. General provisioner syntax ## 6. local-exec provisioner — format & example ## Purpose ## Format ## Example 1: Simple echo message ## What happens? ## Example 2: Save EC2 public IP to file ## 7. remote-exec provisioner — format & explanation ## Purpose ## Key difference ## Format (very important) ## 8. remote-exec — real example (Install NGINX) ## What happens? ## 9. Using BOTH provisioners together (common pattern) ## Real-world flow ## 10. Key takeaways for students ## Memorize this table ## 11. One-sentence interview answer ## Terraform local-exec Provisioner — Practical Lab ## Goal of this lab ## Key rules to remember (before coding) ## Rule 1: Provisioners must be inside a resource ## Rule 2: Syntax format ## Rule 3: local-exec runs on your machine ## Lab file structure ## Full working example: local-exec.tf ## Understanding the important parts ## 1. Why self.private_ip? ## 2. Why echo? ## Running the lab ## Step 1: Initialize Terraform ## Step 2: Apply configuration ## What happens internally ## Verifying the result ## Check file exists ## View file content ## Verify in AWS Console ## Important clarification (very common confusion) ## Real-world use cases of local-exec ## Clean up (important) ## One-sentence summary for students ## Lesson 1: Terraform remote-exec Provisioner — Practical Guide ## Goal of this lab ## Key concept recap (must be clear) ## Step 1: Prerequisites ## 1. Create an EC2 key pair ## 2. Place key in Terraform folder ## 3. Fix key permissions (Mac / Linux only) ## Step 2: Security Group requirements ## Step 3: Full working remote-exec.tf ## Step 4: Why each part matters ## key_name ## private_key = file(...) ## user = "ec2-user" ## Step 5: Run Terraform ## Expected Terraform output flow ## Step 6: Verify in browser ## Step 7: Troubleshooting remote-exec (VERY IMPORTANT) ## Issue 1: SSH connection fails ## Issue 2: Permission denied while installing packages ## Issue 3: Key permission error ## Cleanup (always do this) ## Lesson 2: Important Provisioner Rules (Avoid Confusion) ## Rule 1: Provisioners are NOT limited to EC2 ## Example: IAM user + local-exec ## Rule 2: Multiple provisioners per resource are allowed ## Example ## Rule 3: Provisioners run AFTER resource creation ## Rule 4: Provisioners are NOT recommended for production ## One-sentence summary for students ## Terraform Provisioners — Creation Time, Destroy Time & Failure Behavior ## 1. Creation-time provisioners (default behavior) ## Key rule ## Example: Creation-time provisioner ## What happens? ## Important clarification (common confusion) ## 2. Destroy-time provisioners ## What are destroy-time provisioners? ## How to define a destroy-time provisioner ## Example: Creation + Destroy provisioners together ## Execution behavior ## Real-world destroy-time use cases ## 3. What happens when a provisioner fails? (VERY IMPORTANT) ## Default behavior ## Why Terraform does this ## 4. What is “tainted”? ## Meaning of tainted ## Example: Failed creation-time provisioner ## Result ## 5. Can we allow provisioning to fail but continue? ## Yes — using on_failure ## Default behavior (implicit) ## Override behavior: Continue on failure ## Result ## When should you use on_failure = continue? ## 6. Summary table (very important for students) ## 7. One-sentence exam / interview answer ## 8. Final teaching advice (important) Terraform is great at creating infrastructure, but infrastructure alone is often not enough. 👉 Provisioners solve this second step They allow Terraform to: A provisioner allows Terraform to execute commands or scripts on a local machine or a remote resource during creation or destruction of infrastructure. Terraform follows two clear phases: Terraform waits for provisioning to finish. Terraform uses SSH, just like you do manually. If any of these fail, provisioning fails. Terraform official recommendation: Avoid provisioners when possible Preferred alternatives: Use provisioners when: Provisioners allow Terraform to configure servers after creation by running commands locally or remotely, but they should be used carefully and are not recommended for large production systems. Provisioners allow Terraform to run commands or scripts They help achieve end-to-end infrastructure setup. Today, Terraform officially supports three provisioners: Provisioners MUST be inside a resource block Provisioners cannot exist outside resources. Run commands on the local system where Terraform runs. Run commands inside the remote server. 👉 Requires SSH connection local-exec runs commands on the machine where Terraform executes, while remote-exec runs commands inside the created server using SSH. After an EC2 instance is created by Terraform: Provisioners cannot exist outside a resource block. Not on EC2. Not over SSH. Only on the system where Terraform runs. That’s all you need for this demo. Region note: This example uses us-east-1. If you use another region, update the AMI ID. “Give me the private IP of this EC2 instance” echo prints text to output. When combined with >: It writes the value into a file. Save the EC2 private IP into server_ip.txt on my local machine Always destroy resources after practice: The local-exec provisioner runs commands on the machine where Terraform executes, allowing us to perform local actions like saving instance IPs after resource creation. Access nginx via browser Understand connection block, key pair, and common errors remote-exec has TWO mandatory parts: Without connection, remote-exec will fail. (Required, otherwise SSH fails) Inbound rules must allow: You can create it manually or via Terraform. Region: us-east-1 Update AMI if you use another region. 👉 Manually test SSH using same values: If this fails, Terraform will also fail. Provisioners can be used with any resource. You can define multiple provisioners inside one resource. Execution order = top to bottom If provisioning fails: Preferred alternatives: Provisioners are best for: remote-exec runs commands on a remote server using SSH and requires a connection block, while provisioners can be attached to any Terraform resource and multiple provisioners can exist within a single resource. By default, all provisioners are creation-time provisioners. Creation-time provisioners are one-time setup actions. 👉 Creation-time provisioners will NOT re-run Terraform assumes provisioning already happened. Destroy-time provisioners run before Terraform destroys a resource. They are used for cleanup tasks. You add one line only: They never run together. If a provisioner fails, Terraform apply fails. Because a failed provisioner may leave a resource in a half-configured (unsafe) state. Terraform chooses safety over convenience. Terraform believes the resource is unsafe and must be recreated. You’ll see it in plan output or state. Next apply → Terraform wants to recreate the user. Provisioners support: Use carefully, only when: Creation-time provisioners run only after initial resource creation, destroy-time provisioners run before deletion, and by default a failed provisioner taints the resource unless on_failure = continue is explicitly set. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: Terraform creates EC2 instance Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Terraform creates EC2 instance CODE_BLOCK: Terraform creates EC2 instance CODE_BLOCK: Terraform connects via SSH Runs commands (install, configure, start services) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Terraform connects via SSH Runs commands (install, configure, start services) CODE_BLOCK: Terraform connects via SSH Runs commands (install, configure, start services) COMMAND_BLOCK: provider "aws" { region = "us-east-2" } resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = ["sg-xxxxxxxx"] provisioner "remote-exec" { inline = [ "sudo yum update -y", "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "provisioner-demo" } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: provider "aws" { region = "us-east-2" } resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = ["sg-xxxxxxxx"] provisioner "remote-exec" { inline = [ "sudo yum update -y", "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "provisioner-demo" } } COMMAND_BLOCK: provider "aws" { region = "us-east-2" } resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = ["sg-xxxxxxxx"] provisioner "remote-exec" { inline = [ "sudo yum update -y", "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "provisioner-demo" } } CODE_BLOCK: terraform init Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform init CODE_BLOCK: terraform init CODE_BLOCK: terraform apply -auto-approve Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform apply -auto-approve CODE_BLOCK: terraform apply -auto-approve CODE_BLOCK: 1. EC2 instance is created 2. Terraform waits for SSH 3. SSH connection established 4. Commands executed: - yum install nginx - systemctl start nginx 5. Provisioning completed Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 1. EC2 instance is created 2. Terraform waits for SSH 3. SSH connection established 4. Commands executed: - yum install nginx - systemctl start nginx 5. Provisioning completed CODE_BLOCK: 1. EC2 instance is created 2. Terraform waits for SSH 3. SSH connection established 4. Commands executed: - yum install nginx - systemctl start nginx 5. Provisioning completed CODE_BLOCK: http://<EC2_PUBLIC_IP> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: http://<EC2_PUBLIC_IP> CODE_BLOCK: http://<EC2_PUBLIC_IP> CODE_BLOCK: Welcome to nginx! Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Welcome to nginx! CODE_BLOCK: Welcome to nginx! CODE_BLOCK: resource "aws_instance" "demo" { provisioner "local-exec" { command = "echo Hello" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_instance" "demo" { provisioner "local-exec" { command = "echo Hello" } } CODE_BLOCK: resource "aws_instance" "demo" { provisioner "local-exec" { command = "echo Hello" } } CODE_BLOCK: provisioner "local-exec" { command = "echo Hello" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: provisioner "local-exec" { command = "echo Hello" } CODE_BLOCK: provisioner "local-exec" { command = "echo Hello" } CODE_BLOCK: resource "RESOURCE_TYPE" "NAME" { provisioner "TYPE" { ... } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "RESOURCE_TYPE" "NAME" { provisioner "TYPE" { ... } } CODE_BLOCK: resource "RESOURCE_TYPE" "NAME" { provisioner "TYPE" { ... } } CODE_BLOCK: provisioner "local-exec" { command = "some command" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: provisioner "local-exec" { command = "some command" } CODE_BLOCK: provisioner "local-exec" { command = "some command" } CODE_BLOCK: resource "aws_instance" "demo" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" provisioner "local-exec" { command = "echo Server has been created through Terraform" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_instance" "demo" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" provisioner "local-exec" { command = "echo Server has been created through Terraform" } } CODE_BLOCK: resource "aws_instance" "demo" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" provisioner "local-exec" { command = "echo Server has been created through Terraform" } } CODE_BLOCK: Server has been created through Terraform Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Server has been created through Terraform CODE_BLOCK: Server has been created through Terraform COMMAND_BLOCK: provisioner "local-exec" { command = "echo ${self.public_ip} > server_ip.txt" } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: provisioner "local-exec" { command = "echo ${self.public_ip} > server_ip.txt" } COMMAND_BLOCK: provisioner "local-exec" { command = "echo ${self.public_ip} > server_ip.txt" } CODE_BLOCK: provisioner "remote-exec" { inline = [ "command1", "command2" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: provisioner "remote-exec" { inline = [ "command1", "command2" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } CODE_BLOCK: provisioner "remote-exec" { inline = [ "command1", "command2" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } CODE_BLOCK: resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" key_name = "terraform-key" provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" key_name = "terraform-key" provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } } CODE_BLOCK: resource "aws_instance" "web" { ami = "ami-0f5fcdfbd140e4ab7" instance_type = "t2.micro" key_name = "terraform-key" provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } } COMMAND_BLOCK: provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } provisioner "local-exec" { command = "echo ${self.public_ip} > public_ip.txt" } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } provisioner "local-exec" { command = "echo ${self.public_ip} > public_ip.txt" } COMMAND_BLOCK: provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } provisioner "local-exec" { command = "echo ${self.public_ip} > public_ip.txt" } CODE_BLOCK: provisioner "local-exec" { command = "..." } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: provisioner "local-exec" { command = "..." } CODE_BLOCK: provisioner "local-exec" { command = "..." } CODE_BLOCK: local-exec.tf Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: local-exec.tf CODE_BLOCK: local-exec.tf COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "myec2" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1) instance_type = "t2.micro" provisioner "local-exec" { command = "echo ${self.private_ip} > server_ip.txt" } tags = { Name = "local-exec-demo" } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "myec2" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1) instance_type = "t2.micro" provisioner "local-exec" { command = "echo ${self.private_ip} > server_ip.txt" } tags = { Name = "local-exec-demo" } } COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "myec2" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1) instance_type = "t2.micro" provisioner "local-exec" { command = "echo ${self.private_ip} > server_ip.txt" } tags = { Name = "local-exec-demo" } } CODE_BLOCK: self.private_ip Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: self.private_ip CODE_BLOCK: self.private_ip CODE_BLOCK: self.public_ip Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: self.public_ip CODE_BLOCK: self.public_ip COMMAND_BLOCK: echo VALUE > file.txt Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: echo VALUE > file.txt COMMAND_BLOCK: echo VALUE > file.txt COMMAND_BLOCK: command = "echo ${self.private_ip} > server_ip.txt" Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: command = "echo ${self.private_ip} > server_ip.txt" COMMAND_BLOCK: command = "echo ${self.private_ip} > server_ip.txt" CODE_BLOCK: terraform init Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform init CODE_BLOCK: terraform init CODE_BLOCK: terraform apply -auto-approve Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform apply -auto-approve CODE_BLOCK: terraform apply -auto-approve CODE_BLOCK: ls Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: server_ip.txt Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: server_ip.txt CODE_BLOCK: server_ip.txt CODE_BLOCK: cat server_ip.txt Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: cat server_ip.txt CODE_BLOCK: cat server_ip.txt CODE_BLOCK: 172.31.25.213 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 172.31.25.213 CODE_BLOCK: 172.31.25.213 CODE_BLOCK: terraform destroy -auto-approve Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform destroy -auto-approve CODE_BLOCK: terraform destroy -auto-approve CODE_BLOCK: running → shutting-down → terminated Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: running → shutting-down → terminated CODE_BLOCK: running → shutting-down → terminated CODE_BLOCK: remote-exec.tf terraform-key.pem Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: remote-exec.tf terraform-key.pem CODE_BLOCK: remote-exec.tf terraform-key.pem CODE_BLOCK: chmod 400 terraform-key.pem Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: chmod 400 terraform-key.pem CODE_BLOCK: chmod 400 terraform-key.pem CODE_BLOCK: SSH | 22 | 0.0.0.0/0 HTTP | 80 | 0.0.0.0/0 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: SSH | 22 | 0.0.0.0/0 HTTP | 80 | 0.0.0.0/0 CODE_BLOCK: SSH | 22 | 0.0.0.0/0 HTTP | 80 | 0.0.0.0/0 COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "web" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = [ "sg-xxxxxxxx" # replace with your SG ID ] provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "remote-exec-demo" } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "web" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = [ "sg-xxxxxxxx" # replace with your SG ID ] provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "remote-exec-demo" } } COMMAND_BLOCK: provider "aws" { region = "us-east-1" } resource "aws_instance" "web" { ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 instance_type = "t2.micro" key_name = "terraform-key" vpc_security_group_ids = [ "sg-xxxxxxxx" # replace with your SG ID ] provisioner "remote-exec" { inline = [ "sudo yum install nginx -y", "sudo systemctl start nginx" ] connection { type = "ssh" user = "ec2-user" private_key = file("terraform-key.pem") host = self.public_ip } } tags = { Name = "remote-exec-demo" } } CODE_BLOCK: private_key = "terraform-key.pem" Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: private_key = "terraform-key.pem" CODE_BLOCK: private_key = "terraform-key.pem" CODE_BLOCK: private_key = file("terraform-key.pem") Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: private_key = file("terraform-key.pem") CODE_BLOCK: private_key = file("terraform-key.pem") CODE_BLOCK: terraform init terraform apply -auto-approve Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform init terraform apply -auto-approve CODE_BLOCK: terraform init terraform apply -auto-approve CODE_BLOCK: http://<EC2_PUBLIC_IP> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: http://<EC2_PUBLIC_IP> CODE_BLOCK: http://<EC2_PUBLIC_IP> CODE_BLOCK: Welcome to nginx! Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Welcome to nginx! CODE_BLOCK: Welcome to nginx! COMMAND_BLOCK: ssh -i terraform-key.pem ec2-user@<PUBLIC_IP> Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: ssh -i terraform-key.pem ec2-user@<PUBLIC_IP> COMMAND_BLOCK: ssh -i terraform-key.pem ec2-user@<PUBLIC_IP> COMMAND_BLOCK: yum install nginx -y Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: yum install nginx -y COMMAND_BLOCK: yum install nginx -y COMMAND_BLOCK: sudo yum install nginx -y Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: sudo yum install nginx -y COMMAND_BLOCK: sudo yum install nginx -y CODE_BLOCK: chmod 400 terraform-key.pem Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: chmod 400 terraform-key.pem CODE_BLOCK: chmod 400 terraform-key.pem CODE_BLOCK: terraform destroy -auto-approve Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: terraform destroy -auto-approve CODE_BLOCK: terraform destroy -auto-approve CODE_BLOCK: resource "aws_iam_user" "demo" { name = "test-user" provisioner "local-exec" { command = "echo IAM user created" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_user" "demo" { name = "test-user" provisioner "local-exec" { command = "echo IAM user created" } } CODE_BLOCK: resource "aws_iam_user" "demo" { name = "test-user" provisioner "local-exec" { command = "echo IAM user created" } } CODE_BLOCK: IAM user created Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: IAM user created CODE_BLOCK: IAM user created CODE_BLOCK: resource "aws_iam_user" "demo" { name = "multi-prov-user" provisioner "local-exec" { command = "echo First provisioner" } provisioner "local-exec" { command = "echo Second provisioner" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_user" "demo" { name = "multi-prov-user" provisioner "local-exec" { command = "echo First provisioner" } provisioner "local-exec" { command = "echo Second provisioner" } } CODE_BLOCK: resource "aws_iam_user" "demo" { name = "multi-prov-user" provisioner "local-exec" { command = "echo First provisioner" } provisioner "local-exec" { command = "echo Second provisioner" } } CODE_BLOCK: First provisioner Second provisioner Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: First provisioner Second provisioner CODE_BLOCK: First provisioner Second provisioner CODE_BLOCK: Create resource ↓ Run provisioners Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Create resource ↓ Run provisioners CODE_BLOCK: Create resource ↓ Run provisioners CODE_BLOCK: resource "aws_iam_user" "demo" { name = "creation-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_user" "demo" { name = "creation-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } } CODE_BLOCK: resource "aws_iam_user" "demo" { name = "creation-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } } CODE_BLOCK: when = destroy Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: when = destroy CODE_BLOCK: when = destroy CODE_BLOCK: resource "aws_iam_user" "demo" { name = "lifecycle-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } provisioner "local-exec" { when = destroy command = "echo This is destroy-time provisioner" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_user" "demo" { name = "lifecycle-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } provisioner "local-exec" { when = destroy command = "echo This is destroy-time provisioner" } } CODE_BLOCK: resource "aws_iam_user" "demo" { name = "lifecycle-user" provisioner "local-exec" { command = "echo This is creation-time provisioner" } provisioner "local-exec" { when = destroy command = "echo This is destroy-time provisioner" } } COMMAND_BLOCK: resource "aws_iam_user" "demo" { name = "tainted-user" provisioner "local-exec" { command = "echo1" # invalid command } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: resource "aws_iam_user" "demo" { name = "tainted-user" provisioner "local-exec" { command = "echo1" # invalid command } } COMMAND_BLOCK: resource "aws_iam_user" "demo" { name = "tainted-user" provisioner "local-exec" { command = "echo1" # invalid command } } CODE_BLOCK: on_failure = continue Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: on_failure = continue CODE_BLOCK: on_failure = continue COMMAND_BLOCK: on_failure = fail # default Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: on_failure = fail # default COMMAND_BLOCK: on_failure = fail # default CODE_BLOCK: resource "aws_iam_user" "demo" { name = "non-tainted-user" provisioner "local-exec" { command = "echo1" on_failure = continue } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_user" "demo" { name = "non-tainted-user" provisioner "local-exec" { command = "echo1" on_failure = continue } } CODE_BLOCK: resource "aws_iam_user" "demo" { name = "non-tainted-user" provisioner "local-exec" { command = "echo1" on_failure = continue } } - Terraform creates an EC2 instance - ❌ EC2 is empty - ❌ No nginx, no app, no configuration - ❌ You still need to log in manually - Run commands - Install software - Configure the server after the resource is created - Stage 1: Terraform creates infrastructure - Stage 2: Terraform configures it - ❌ Provisioners are NOT part of new Terraform exams - You will not be tested on them - ✅ Still heavily used in many companies - ✅ Very common for: Bootstrap scripts Temporary automation Legacy workflows Learning environments - Bootstrap scripts - Temporary automation - Legacy workflows - Learning environments - Bootstrap scripts - Temporary automation - Legacy workflows - Learning environments - Creates EC2 - SSHs into it - Installs nginx - Starts nginx - You open browser → see Welcome to nginx - AWS key pair already created - .pem file on your laptop - Port 22 and 80 allowed in Security Group - Username → ec2-user - Private key → .pem file - IP address → self.public_ip - Copy EC2 public IP - Open browser: - EC2 created - Software installed - Service running - ❌ Hard to debug - ❌ Not idempotent - ❌ Failures can break apply - ❌ Mixing infra + config - Configuration management tools - Learning Terraform - Quick demos - One-time bootstrap - Legacy systems - No config tool available - Large production systems - Long-lived infrastructure - on the local machine (where Terraform runs) - or on a remote machine (like an EC2 instance) - during resource creation or destruction - Older provisioners like Chef, Puppet, Salt were removed - Only these three remain - Most commonly used: local-exec and remote-exec - Runs on your laptop / CI / Jenkins - Does NOT touch the server - Used for: Saving IPs to files Running curl, ping Calling scripts or APIs - Saving IPs to files - Running curl, ping - Calling scripts or APIs - Saving IPs to files - Running curl, ping - Calling scripts or APIs - Runs inside the server - Requires SSH access - Used for: Installing software Configuring OS Starting services - Installing software - Configuring OS - Starting services - Installing software - Configuring OS - Starting services - provisioner "local-exec" - provisioner "remote-exec" - provisioner "file" - EC2 is created - Terraform prints: - A file server_ip.txt is created locally - It contains the EC2 public IP - EC2 is created - Terraform connects via SSH - NGINX is installed - Web server starts automatically - remote-exec → configure server - local-exec → save IP locally - Website running - IP saved locally - Browser access works - Capture the IP address of the instance - Store it locally in a file called server_ip.txt - Understand: where local-exec runs what self means why provisioners must be inside resources - where local-exec runs - what self means - why provisioners must be inside resources - where local-exec runs - what self means - why provisioners must be inside resources - self → refers to this resource - private_ip → attribute of aws_instance - Terraform creates EC2 - EC2 gets a private IP - local-exec runs on your laptop - File server_ip.txt is created - IP address is written into the file - Open EC2 → Instance → Networking - Private IP should match the file content - Save IPs or DNS names to files - Run Ansible after infra creation - Trigger shell scripts - Call APIs (curl) - Notify Slack / email - Run tests from CI/CD - Create an EC2 instance - Use remote-exec to: Connect via SSH Install nginx Start nginx - Connect via SSH - Install nginx - Start nginx - Access nginx via browser - Understand connection block, key pair, and common errors - Connect via SSH - Install nginx - Start nginx - connection block → how Terraform connects - provisioner block → what commands to run - Name: terraform-key - Downloaded file: terraform-key.pem - SSH → port 22 - HTTP → port 80 - EC2 must be created using the same key - Otherwise SSH login will fail - Terraform expects key contents, not filename - This is a very common mistake - EC2 created - Terraform waits for SSH - SSH connected - Nginx installed - Nginx started - Provisioning completed - ec2-user is not root - Always use sudo - Resource still exists - Terraform marks apply as failed - Not idempotent - Hard to debug - Can break pipelines - Mix infra + config - Configuration management tools - Temporary automation - Legacy workflows - They run only once - They run after the resource is created - They do NOT run on updates - They do NOT run on every apply - Change EC2 size - Update tags - Modify metadata - De-register EC2 from antivirus / monitoring - Remove instance from CMDB - Revoke licenses - Notify external systems before deletion - Cleanup external dependencies - Resource is marked as tainted - Next terraform apply will destroy & recreate the resource - EC2 created - Application installation failed - Instance is useless - Terraform plans destroy + recreate - Happens automatically on next apply - User is created - Provisioner fails - Resource marked tainted - Terraform apply fails - Fails apply - Marks resource tainted - Provisioner fails - Terraform apply continues - Resource NOT tainted - Terraform state is clean - Provisioner is non-critical - Logging, notification, best-effort actions - Optional integrations - Software installation - Security setup - Application configuration - Are powerful - Are not idempotent - Are not recommended for large production systems - Use them for learning, demos, cleanup hooks - Use user_data, Ansible, cloud-init for real production