Skip to main content

Fix Terraform Error: Local-Exec Provisioner Failed

Key Takeaway

Fix terraform local-exec provisioner failures. Debug exit codes, fix interpreter settings, handle working directories, and use on_failure for non-critical scripts.

Table of Contents

Quick Answer

# Test the command locally first
bash -c 'your-command-here'
echo $?  # Check exit code (0 = success)

The Error

Error: local-exec provisioner error

  with aws_instance.web,
  on main.tf line 25, in resource "aws_instance" "web":
  25:   provisioner "local-exec" {

Error running command 'kubectl apply -f k8s/': exit status 1

Or:

Error: local-exec provisioner error

Error running command '/bin/sh -c "apt-get install -y jq"': exit status 100

What Causes This

  1. Command not found — binary isn’t installed or not in PATH
  2. Non-zero exit code — script failed (permission denied, syntax error, etc.)
  3. Wrong shell/interpreter — default is /bin/sh, but command needs bash
  4. Missing dependencies — command depends on tools not available in the execution environment
  5. Working directory wrong — script references relative paths
  6. CI/CD environment differs — works locally but not in runner

Solution 1: Test the Command First

# Run the exact command Terraform would run
/bin/sh -c 'your-command-here'
echo "Exit code: $?"

If it fails locally, fix the command before putting it in Terraform.

Solution 2: Set the Correct Interpreter

provisioner "local-exec" {
  # Default: ["/bin/sh", "-c"]
  # For bash features (arrays, [[ ]], etc.):
  interpreter = ["/bin/bash", "-c"]
  command     = <<-EOT
    set -euo pipefail
    if [[ -f config.json ]]; then
      jq '.version' config.json
    fi
  EOT
}

For Python:

provisioner "local-exec" {
  interpreter = ["python3", "-c"]
  command     = "import json; print(json.dumps({'status': 'ok'}))"
}

For Windows:

provisioner "local-exec" {
  interpreter = ["PowerShell", "-Command"]
  command     = "Get-Process | Where-Object {$_.CPU -gt 100}"
}

Solution 3: Set Working Directory

provisioner "local-exec" {
  working_dir = "${path.module}/scripts"
  command     = "./deploy.sh"
}

Solution 4: Pass Environment Variables

provisioner "local-exec" {
  command = "ansible-playbook -i '${self.public_ip},' playbook.yml"

  environment = {
    ANSIBLE_HOST_KEY_CHECKING = "False"
    ENV_NAME                  = terraform.workspace
    DB_ENDPOINT               = aws_db_instance.main.endpoint
  }
}

Solution 5: Use on_failure for Non-Critical Scripts

provisioner "local-exec" {
  command    = "curl -X POST https://slack.webhook.url -d '{\"text\": \"Deploy complete\"}'"
  on_failure = continue   # Don't fail the resource if Slack notification fails
}

on_failure options:

  • fail (default) — resource creation fails
  • continue — log the error but continue

Solution 6: Proper Error Handling

provisioner "local-exec" {
  interpreter = ["/bin/bash", "-c"]
  command     = <<-EOT
    set -euo pipefail  # Exit on error, undefined vars, pipe failures

    echo "Waiting for instance to be ready..."
    for i in $(seq 1 30); do
      if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no \
         ubuntu@${self.public_ip} 'echo ready' 2>/dev/null; then
        echo "Instance is ready"
        exit 0
      fi
      echo "Attempt $i/30 - waiting..."
      sleep 10
    done

    echo "ERROR: Instance not ready after 5 minutes"
    exit 1
  EOT
}

Better Alternatives to Provisioners

HashiCorp recommends avoiding provisioners when possible:

# ❌ Provisioner
provisioner "local-exec" {
  command = "aws s3 cp config.json s3://my-bucket/"
}

# ✅ Native resource
resource "aws_s3_object" "config" {
  bucket = "my-bucket"
  key    = "config.json"
  source = "config.json"
}
# ❌ Provisioner for user data
provisioner "remote-exec" {
  inline = ["apt-get update && apt-get install -y nginx"]
}

# ✅ cloud-init user data
resource "aws_instance" "web" {
  user_data = <<-EOF
    #!/bin/bash
    apt-get update && apt-get install -y nginx
  EOF
}

Debugging

# Enable debug logging to see the full command output
export TF_LOG=DEBUG
terraform apply 2>&1 | grep -A 20 "local-exec"

Hands-On Courses

Conclusion

Local-exec provisioner errors mean the command returned a non-zero exit code. Test the command locally first, set the correct interpreter (bash vs sh), pass required environment variables, and use on_failure = continue for non-critical scripts. When possible, replace provisioners with native Terraform resources or cloud-init user data.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.