Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Terraform Error: 'local-exec provisioner error' command exited with a non-zero status

Quick answer

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
Free toolkit

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 PATH in the environment Terraform runs in (common in CI where aws, kubectl, or jq are not installed).
  • The script path is wrong because local-exec runs from Terraform’s working directory, not the module directory, unless you set working_dir.
  • The script itself exits non-zero — a failed API call, a set -e line 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 interpreter is wrong (e.g. assuming /bin/bash exists).

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

  1. 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.

  2. 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.

  3. Fix the working directory so relative script paths resolve. Set working_dir explicitly 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
    }
  }
}
  1. 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
  1. Pass required credentials through the environment map instead of relying on inherited shell state, especially in CI where the parent environment differs.

  2. Because the failed provisioner tainted the resource, re-running terraform apply will recreate it and re-run the provisioner. Confirm the plan shows the resource being replaced, then apply:

terraform apply
  1. 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.module when running scripts shipped with the module.
  • Pin the interpreter and add a shebang so behavior is identical across machines.
  • Pass every dependency (binaries, tokens, region) explicitly via environment rather than inheriting it.
  • Test provisioner scripts in isolation in CI before wiring them into an apply.
  • Add set -euo pipefail to scripts so they fail loudly and early instead of half-completing.
  • 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.

Free download · 368-page PDF

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.