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
- Command not found — binary isn’t installed or not in PATH
- Non-zero exit code — script failed (permission denied, syntax error, etc.)
- Wrong shell/interpreter — default is
/bin/sh, but command needsbash - Missing dependencies — command depends on tools not available in the execution environment
- Working directory wrong — script references relative paths
- 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 failscontinue— 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
- Terraform for Beginners on CopyPasteLearn
- Terraform By Example — practical code examples
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.