Terraform Error: 'local-exec provisioner error' command exited with a non-zero status
Fix Terraform's local-exec provisioner 'Error running command ... exit status N' failure: diagnose the underlying command, PATH, working directory, and interpreter issues, and recover state.
- #terraform
- #iac
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Exact Error Message
╷
│ Error: local-exec provisioner error
│
│ with null_resource.bootstrap,
│ on main.tf line 6, in resource "null_resource" "bootstrap":
│ 6: provisioner "local-exec" {
│
│ Error running command './scripts/register.sh': exit status 1. Output:
│ ./scripts/register.sh: line 4: aws: command not found
╵
The exit status and the trailing Output: lines change depending on what your command actually printed, but the shape is always Error running command '<cmd>': exit status N.
What It Means
A local-exec provisioner runs a command on the machine executing Terraform — your laptop, a CI runner, or a bastion — not on the resource being created. Terraform treats any non-zero exit code from that command as a hard failure. When the command fails, the provisioner fails, and because the provisioner ran on a resource that was being created, that resource is marked tainted and will be destroyed and recreated on the next apply.
The message includes the failing command and its captured output, which is almost always enough to see the real problem: a missing binary, a bad path, a script that returned non-zero, or a missing environment variable.
Common Causes
- The invoked binary is not on
PATHin the environment Terraform runs in (common in CI whereaws,kubectl, orjqare not installed). - The script path is wrong because
local-execruns from Terraform’s working directory, not the module directory, unless you setworking_dir. - The script itself exits non-zero — a failed API call, a
set -eline that tripped, or a bad argument. - The script is not executable (
permission denied) or lacks a shebang and the default interpreter cannot run it. - Required environment variables (credentials, tokens) are not exported to the provisioner.
- On Windows/macOS/Linux differences, the
interpreteris wrong (e.g. assuming/bin/bashexists).
Diagnostic Commands
Run the exact command by hand from the same directory Terraform uses to reproduce the failure:
cd path/to/module && ./scripts/register.sh; echo "exit=$?"
Confirm the binary the script needs is actually available in this shell:
which aws jq kubectl
Re-run the apply with debug logging to capture the full provisioner invocation and environment:
TF_LOG=DEBUG terraform apply 2>&1 | tee apply-debug.log
Check the resource’s taint status after a failed apply:
terraform state show null_resource.bootstrap
terraform plan
Step-by-Step Resolution
-
Read the captured
Output:in the error — it usually names the exact failure (command not found,permission denied, an API error). Reproduce it with the diagnostic command above. -
If the binary is missing, install it in the execution environment or reference it by absolute path. In CI, add it to the runner image rather than assuming it exists.
-
Fix the working directory so relative script paths resolve. Set
working_direxplicitly and pass the module path:
resource "null_resource" "bootstrap" {
provisioner "local-exec" {
working_dir = path.module
command = "./scripts/register.sh"
interpreter = ["/bin/bash", "-c"]
environment = {
AWS_REGION = var.region
API_TOKEN = var.api_token
}
}
}
- Make the script executable and give it a shebang so the interpreter is unambiguous:
chmod +x scripts/register.sh
head -1 scripts/register.sh # should be: #!/usr/bin/env bash
-
Pass required credentials through the
environmentmap instead of relying on inherited shell state, especially in CI where the parent environment differs. -
Because the failed provisioner tainted the resource, re-running
terraform applywill recreate it and re-run the provisioner. Confirm the plan shows the resource being replaced, then apply:
terraform apply
- If the command is best-effort and its failure should not block Terraform, wrap it so it always exits zero (for example append
|| true), but do this deliberately — it hides real errors.
Prevention
- Prefer native providers or
terraform_data/API resources over shell-outs; provisioners are a last resort per HashiCorp guidance. - Always set
working_dir = path.modulewhen running scripts shipped with the module. - Pin the
interpreterand add a shebang so behavior is identical across machines. - Pass every dependency (binaries, tokens, region) explicitly via
environmentrather than inheriting it. - Test provisioner scripts in isolation in CI before wiring them into an apply.
- Add
set -euo pipefailto scripts so they fail loudly and early instead of half-completing.
Related Errors
remote-exec provisioner error: timeout— the SSH/WinRM variant that fails on connection rather than exit code.Error running command ... exit status 127— specifically a missing binary (command not found).permission denied— the script is not executable or the interpreter cannot read it.Resource is tainted— the follow-on state condition after a provisioner fails.
Frequently Asked Questions
Why did my resource get destroyed after the provisioner failed? A provisioner failure during create taints the resource, so Terraform plans to destroy and recreate it on the next apply. Fix the command, then re-apply.
How do I stop a non-critical command from failing the apply? Set the on_failure = continue argument on the provisioner, or make the command exit zero — but only when the failure genuinely does not matter.
Where does local-exec actually run? On the machine running Terraform (your workstation or CI runner), never on the created resource. Use remote-exec for the resource itself.
Why does it work locally but fail in CI? The CI runner usually has a different PATH, missing binaries, and no inherited credentials. Install dependencies in the runner and pass secrets via environment. For more automation patterns, see the prompt library.
Can I capture the command output for debugging? Yes — run with TF_LOG=DEBUG, which logs the full invocation and captured stdout/stderr. For more, browse the Terraform guides.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.