Ignition
Guides

Resources Overview

The six built-in resources and how they work.

Resources are the building blocks of Ignition recipes. Each resource manages a specific aspect of server state.

The six resources

ResourcePurposeNature
aptManage system packagesDeclarative
dockerManage Docker containersMixed
fileManage file contents, permissions, ownershipDeclarative
directoryManage directoriesDeclarative
execRun arbitrary commandsImperative
serviceManage systemd servicesMixed

Declarative vs imperative

Declarative resources describe a desired end state. Ignition checks the current state and only makes changes if needed:

// Declarative: "nginx should be installed"
await apt({ name: "nginx", state: "present" })
// If already installed → ok (no action)
// If not installed → installs it → changed

Imperative resources always execute their action:

// Imperative: "run this command"
await exec({ command: "npm install" })
// Always runs → always reports changed

The service resource is mixed — started and stopped are declarative (they check first), while restarted and reloaded are imperative (they always execute).

The docker resource is also mixed — the lifecycle states are declarative by default, but pull: "always" intentionally forces apply mode to refresh the image reference on every run.

Creating resources

Use createResources(ctx) to get bound resource functions:

import type { ExecutionContext } from "@grovemotorco/ignition"
import { createResources } from "@grovemotorco/ignition"

export default async function (ctx: ExecutionContext) {
  const { apt, docker, file, directory, exec, service } = createResources(ctx)

  // Each function is bound to the current ExecutionContext
  await apt({ name: "curl", state: "present" })
  await docker({ name: "cache", image: "redis:7" })
}

Resource results

Every resource call returns a ResourceResult:

const result = await apt({ name: "nginx", state: "present" })

result.status // "ok" | "changed" | "failed"
result.type // "apt"
result.name // "nginx"
result.durationMs // execution time in milliseconds
result.current // observed state before execution (undefined on failure)
result.desired // target state from input (undefined on failure)
result.output // resource-specific output data (undefined on failure)

Status meanings

StatusMeaning
okAlready in the desired state. No changes made.
changedWas not in desired state. Changes were applied (or would be, in check mode).
failedAn error occurred during check or apply.

Resource call metadata

Pass metadata as the second argument to tag, identify, or annotate resource calls:

await apt({ name: "nginx", state: "present" }, { tags: ["web"], id: "install-nginx" })
FieldTypePurpose
tagsstring[]Call-level labels for programmatic filtering
idstringUnique identifier for this call
notifystring[]Notification labels (for future use)
sensitivePathsstring[]Redaction hints; not automatically enforced by the current CLI

CLI --tags filtering applies to recipe meta.tags, not per-resource call metadata.

Check-then-apply lifecycle

Every resource follows a two-phase cycle managed by executeResource():

  1. check() — inspects current state and decides whether changes are needed. For built-in resources, this phase is read-only and safe to run under ignition run --check.
  2. apply() — mutating. Only called if check() returned inDesiredState: false and the mode is apply.

This gives you:

  • Safe dry-runsignition run --check runs check() for every resource without calling apply()
  • Safe re-runs — if everything is already correct, nothing changes
  • Explicit failures — any resource that can't converge reports failed

Imperative resources can still be conservative. For example, unguarded exec, guarded exec with apply-time preconditions (unless / onlyIf), and service with state: "restarted" or "reloaded" may report "would change" in check mode because they cannot prove the final outcome from read-only checks alone.

exec also exposes unsafeCheckUnless and unsafeCheckOnlyIf as explicit escape hatches when you intentionally want a command to run during check() and accept dry-run side effects.

When an exec command needs setup such as sourcing NVM or exporting vars, use shell to run a POSIX shell preamble before the main command. The preamble shares the same shell session as the command, and the same sudo, cwd, and env settings also apply to unless / onlyIf guards.

For the full API of each resource, see the individual reference pages:

On this page