ReferenceResources
exec
Run arbitrary commands on remote hosts.
The exec resource runs commands on the remote host via SSH. It is imperative by default, with apply-time preconditions (unless / onlyIf) for conditional execution and explicitly unsafe check-time escape hatches when you need them.
Input
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
command | string | — | Yes | The command to run |
sudo | boolean | false | No | Run with sudo sh -c |
cwd | string | — | No | Working directory |
shell | string | — | No | POSIX shell preamble run before command in the same shell session |
env | Record<string, string> | — | No | Environment variables |
check | boolean | true | No | If false, non-zero exit codes are not failures |
unless | string | — | No | Skip apply when this precondition exits 0; evaluated in apply() |
onlyIf | string | — | No | Run apply only when this precondition exits 0; evaluated in apply() |
unsafeCheckUnless | string | — | No | Unsafe: skip apply when this command exits 0 during check() |
unsafeCheckOnlyIf | string | — | No | Unsafe: run apply only when this command exits 0 during check() |
Output
| Field | Type | Description |
|---|---|---|
exitCode | number | Exit code of the command |
stdout | string | Standard output |
stderr | string | Standard error |
Behavior
Check phase
check() is read-only by default. It does not execute command, unless, or onlyIf.
By default:
- unguarded
execreturnsinDesiredState: false unless/onlyIfalso returninDesiredState: falseignition run --checktherefore reports "would change" conservatively for those cases
Unsafe opt-ins:
unsafeCheckUnless: if the command exits0, the resource is treated as already satisfiedunsafeCheckOnlyIf: if the command exits non-zero, apply is skippedunsafeCheckUnlessandunsafeCheckOnlyIfexecute duringcheck(), includingignition run --check- only one of
unless,onlyIf,unsafeCheckUnless, andunsafeCheckOnlyIfmay be set
Apply phase
- Evaluates
unless/onlyIffirst, if present. - Builds the command string:
- Wraps with
cd <cwd> &&ifcwdis specified - Prepends environment variables as
KEY=VALUEpairs - If
shellis set, runsshellandcommandinside the samesh -csession - Wraps with
sudo sh -c '...'ifsudois true
- Wraps with
- Executes the command via SSH.
- If the command exits with non-zero and
checkistrue, throwsSSHCommandError. - If
checkisfalse, the exit code is captured but not treated as a failure.
Guard commands inherit sudo, cwd, env, and shell from the parent input. A failing shell
preamble aborts guard evaluation instead of being treated like a false unless / onlyIf.
Annotations
| Property | Value |
|---|---|
| Nature | Imperative |
| Idempotent | No |
| Destructive | Yes |
| Read-only | No |
| Required capabilities | exec |
Examples
Basic command
await exec({ command: "nginx -t" })With sudo
await exec({ command: "systemctl daemon-reload", sudo: true })With working directory
await exec({ command: "npm install --production", cwd: "/opt/app" })With environment variables
await exec({
command: "npm run build",
cwd: "/opt/app",
env: { NODE_ENV: "production", CI: "true" },
})With a shell preamble
await exec({
command: "nvm install 24 && nvm alias default 24",
shell: 'export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh"',
unless: "node --version | grep -q '^v24'",
})Tolerating non-zero exit codes
await exec({ command: "grep -q 'done' /tmp/flag", check: false })With check: false, the resource won't fail even if the command returns a non-zero exit code. The exit code is still captured in the output.
Guarded execution
await exec({
command: "npm install -g pm2",
unless: "command -v pm2",
sudo: true,
})Unsafe check-time probe
await exec({
command: "npm install -g pm2",
unsafeCheckUnless: "command -v pm2",
sudo: true,
})Capturing output
const result = await exec({ command: "cat /etc/hostname" })
const hostname = result.output?.stdout.trim()Gotchas
- Unguarded
execalways reportschangedbecause it can't determine whether the command mutated state. - In check mode (
ignition run --check), unguarded exec reports as "would change" since it's imperative. unlessandonlyIfare apply-time preconditions, so check mode still reports them as "would change".unlesscan reportokduring apply when the precondition shows the desired state is already met.onlyIfcan also reportok, but that means the precondition failed and apply was skipped.unsafeCheckUnlessandunsafeCheckOnlyIfare the only ways to run user commands duringcheck(); use them only when you intentionally accept dry-run side effects.- Set
check: falsefor commands where a non-zero exit code is acceptable (e.g.,grepreturning 1 for no match). sudowraps the entire command withsudo sh -c '...'— shell features like pipes and redirects work inside.shellruns through POSIXsh -c, not Bash. Use portable shell syntax, or invokebash -lc '...'yourself when you need Bash-specific behavior.command,shell, and guard fields are trusted shell snippets. Do not pass untrusted input into them without your own validation/escaping.- Command output (stdout/stderr) can be streamed in real time when trace mode is enabled.