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