The local-exec provisioner runs commands on the machine executing Terraform, not on the target resources. This introduces unique error patterns around command execution, permissions, and environment handling.

Understanding Local-Exec Errors

Common local-exec errors: `` Error: Error running command: exit status 1 Error: command not found: ./script.sh Error: Permission denied: cannot execute Error: environment variable not found: AWS_ACCESS_KEY

Issue 1: Command Exit Code Failures

Commands return non-zero exit codes.

Error Example: `` Error: Error running command './deploy.sh': exit status 1

Root Cause: ``hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = "./deploy.sh" # Script exits with non-zero } }

Solution:

Debug the command: ``bash # Run the command manually ./deploy.sh echo $? # Check exit code

Add error handling in the script: ```hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = <<-EOT set -e # Exit on error ./deploy.sh || { echo "Deploy failed, but continuing" exit 0 # Don't fail Terraform } EOT

on_failure = continue # Alternative: don't fail on error } } ```

Better script with proper error handling: ```bash #!/bin/bash set -e

log() { echo "[$(date)] $1" }

log "Starting deployment"

# Check prerequisites if ! command -v aws &> /dev/null; then log "ERROR: AWS CLI not found" exit 1 fi

# Main deployment log "Deploying application..." aws s3 sync ./dist s3://my-bucket/app/

log "Deployment complete" ```

Issue 2: Command Not Found

Specified command or script doesn't exist.

Error Example: `` Error: Error running command './scripts/deploy.sh': fork/exec ./scripts/deploy.sh: no such file or directory

Solution:

Verify path and file existence: ```bash # Check if script exists ls -la ./scripts/deploy.sh

# Check working directory pwd ```

Use absolute paths: ```hcl resource "null_resource" "deploy" { provisioner "local-exec" { # Use absolute path command = "${path.module}/scripts/deploy.sh"

# Or use path.root for repository root # command = "${path.root}/scripts/deploy.sh" } } ```

Or use inline commands: ```hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = <<-EOT chmod +x ${path.module}/scripts/deploy.sh ${path.module}/scripts/deploy.sh EOT

# Or run directly with interpreter interpreter = ["bash", "-c"] } } ```

Issue 3: Permission Denied

Cannot execute script due to permission issues.

Error Example: `` Error: Error running command './script.sh': fork/exec ./script.sh: permission denied

Solution:

Fix script permissions: ``hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = <<-EOT chmod +x ${path.module}/scripts/deploy.sh ${path.module}/scripts/deploy.sh EOT } }

Or use interpreter: ``hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = "${path.module}/scripts/deploy.sh" interpreter = ["bash", "-c"] } }

Or specify interpreter inline: ``hcl resource "null_resource" "deploy" { provisioner "local-exec" { interpreter = ["python3", "-c"] command = <<-EOT import subprocess subprocess.run(['./deploy.sh']) EOT } }

Issue 4: Environment Variable Issues

Required environment variables missing or incorrect.

Error Example: `` Error: Error running command 'aws s3 ls': An error occurred (AccessDenied) when calling the ListBuckets operation

Solution:

Pass environment variables explicitly: ```hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = "aws s3 sync ./dist s3://${var.bucket_name}/"

environment = { AWS_ACCESS_KEY_ID = var.aws_access_key AWS_SECRET_ACCESS_KEY = var.aws_secret_key AWS_REGION = var.aws_region

# Custom variables APP_VERSION = var.app_version DEPLOY_ENV = terraform.workspace } } } ```

Use Terraform outputs: ```hcl resource "null_resource" "configure" { provisioner "local-exec" { command = "./configure.sh"

environment = { INSTANCE_IP = aws_instance.web.public_ip INSTANCE_ID = aws_instance.web.id DB_ENDPOINT = aws_db_instance.main.endpoint } } } ```

Issue 5: Working Directory Problems

Commands run from wrong directory.

Error Example: `` Error: Error running command './build.sh': Cannot find 'src/' directory

Solution:

Specify working directory: ```hcl resource "null_resource" "build" { provisioner "local-exec" { command = "./build.sh" working_dir = path.module # Module directory

# Or specify absolute path # working_dir = "/home/user/project" } } ```

Handle directory changes in command: ``hcl resource "null_resource" "build" { provisioner "local-exec" { command = <<-EOT cd ${path.module}/src npm install npm run build EOT } }

Issue 6: Output Parsing Errors

Cannot parse command output correctly.

Error Example: `` Error: Error running command 'get-value.sh': Expected JSON output but got plain text

Solution:

Use jq for JSON parsing: ```hcl resource "null_resource" "extract_value" { provisioner "local-exec" { command = <<-EOT # Ensure JSON output output=$(get-value.sh --format json)

# Extract specific field value=$(echo "$output" | jq -r '.value')

# Save for later use echo "$value" > ${path.module}/output.txt EOT } }

# Read the output file for use in Terraform locals { extracted_value = file("${path.module}/output.txt") } ```

Better approach using external data source: ```hcl data "external" "get_value" { program = ["bash", "-c", "get-value.sh --format json"] }

output "value" { value = data.external.get_value.result.value } ```

Issue 7: Long-Running Command Timeout

Commands take longer than Terraform expects.

Error Example: `` Error: Error running command 'build-large-app.sh': context deadline exceeded

Solution:

Handle long-running processes: ```hcl resource "null_resource" "long_build" { provisioner "local-exec" { command = <<-EOT # Run in background with output capture nohup ./build-large-app.sh > build.log 2>&1 &

# Wait for completion while ! grep "BUILD_COMPLETE" build.log; do sleep 10 if grep "BUILD_FAILED" build.log; then exit 1 fi done EOT

# Or use async tools # command = "./build-async.sh --timeout 3600" } } ```

Issue 8: Destroy Provisioner Timing

Commands run at wrong time during destroy.

Error Example: `` Error: Destroy provisioner failed - resource already gone

Solution:

Handle destroy timing properly: ```hcl resource "null_resource" "cleanup" { provisioner "local-exec" { when = destroy command = <<-EOT # Use stored information for cleanup if [ -f "${path.module}/resource-info.txt" ]; then RESOURCE_ID=$(cat ${path.module}/resource-info.txt) ./cleanup.sh $RESOURCE_ID fi EOT

on_failure = continue # Don't fail Terraform if cleanup fails }

provisioner "local-exec" { when = create command = <<-EOT # Store resource information for later cleanup echo "${aws_instance.web.id}" > ${path.module}/resource-info.txt EOT } } ```

Issue 9: Multi-line Command Issues

Long commands fail to parse correctly.

Error Example: `` Error: Error running command: unexpected EOF

Solution:

Use heredoc syntax properly: ```hcl resource "null_resource" "deploy" { provisioner "local-exec" { command = <<-EOT # Multi-line script set -e

echo "Starting deployment" ./prepare.sh ./deploy.sh --version ${var.app_version} ./verify.sh

echo "Deployment complete" EOT } } ```

Avoid inline continuation: ```hcl # Wrong - hard to read and prone to errors # command = "echo start && ./deploy.sh && echo done"

# Right - use heredoc command = <<-EOT echo "start" ./deploy.sh echo "done" EOT ```

Verification Steps

Debug local-exec commands: ```bash # Enable debug logging export TF_LOG=DEBUG terraform apply

# Test commands manually in same environment cd $(terraform workspace show) ./your-script.sh ```

Check command output: ``bash # Run terraform with verbose output terraform apply -verbose

Prevention Best Practices

  1. 1.Use absolute paths with ${path.module} or ${path.root}
  2. 2.Handle errors in scripts with proper exit codes
  3. 3.Pass credentials via environment block, not command arguments
  4. 4.Use heredoc syntax for multi-line commands
  5. 5.Add on_failure = continue for non-critical operations
  6. 6.Use external data source instead of file outputs
  7. 7.Test commands manually before adding to Terraform
  8. 8.Avoid local-exec for critical operations - use cloud-native tools instead