Manage Lifecycle Events with Hooks
Atmos hooks run automated actions at specific points in a component's
lifecycle — before/after terraform plan, apply, deploy, or init.
Built-in kinds cover the common cases (cost analysis, security scanning,
storing outputs) and the generic command kind plugs in any binary you
need. Tools auto-install through the Atmos toolchain and their output
renders identically in your terminal and on Atmos Pro.
Hooks Schema
Each hook has a kind that selects an engine and a set of defaults. The
generic command kind runs any binary; named kinds like infracost and
trivy ship sane defaults so you don't need to write any wiring.
components:
terraform:
vpc:
hooks:
# Cost diff after every plan — zero config.
cost:
events: [after-terraform-plan]
kind: infracost
# Security scan after every plan.
security:
events: [after-terraform-plan]
kind: trivy
# Store outputs after apply (the original hook use case).
outputs:
events: [after-terraform-apply]
kind: store
name: prod/ssm
outputs:
vpc_id: .id
Hooks can be declared at the global level, the terraform section, an
individual component, or in the overrides section. Partial config at
each level merges into the final result so you can keep configuration
DRY.
Supported Lifecycle Events
| Event | When it fires |
|---|---|
before-terraform-init | Before atmos terraform init runs |
before-terraform-plan | Before atmos terraform plan runs (after init has completed) |
after-terraform-plan | After atmos terraform plan finishes |
before-terraform-apply | Before atmos terraform apply runs |
after-terraform-apply | After atmos terraform apply finishes |
before-terraform-deploy | Alias of before-terraform-apply (deploy is apply with -auto-approve) |
after-terraform-deploy | Alias of after-terraform-apply |
Hooks with no events list fire on every supported event for backward
compatibility with configs written before event filtering existed. You
can use the dotted form (after.terraform.plan) or the hyphenated form
(after-terraform-plan) — they're equivalent.
Built-in Kinds
Each named kind below ships defaults for command, args, env, and
on_failure so the minimal config (just events and kind) does the
right thing. Every default is overridable — set the field on your hook
and your value wins. See Overriding Kind Defaults
for the override rules (notably: args and env are full replacement,
not merge).
kind: infracost
Runs Infracost to compute monthly cloud cost estimates from your Terraform plan. Renders a cost diff card with the top resources by monthly cost.
hooks:
cost:
events: [after-terraform-plan]
kind: infracost
Requires INFRACOST_API_KEY to be set in the shell or on the hook
(free at infracost.io).
kind: trivy
Trivy is an open-source security and misconfiguration
scanner from Aqua Security. The trivy kind
runs Trivy in config mode against the component's Terraform sources and
feeds the SARIF output through Atmos's shared scanner result handler.
hooks:
security:
events: [after-terraform-plan]
kind: trivy
command(default)trivy.args(default)config --format sarif --output $ATMOS_OUTPUT_FILE --quiet $ATMOS_COMPONENT_PATH.- Output
- Single SARIF file written directly to
$ATMOS_OUTPUT_FILE. on_failure(default)warn— a scanner finding does not block your plan unless you override this.- Runtime requirements
- The
trivybinary on PATH. Declare it independencies.toolsto have Atmos auto-install through the toolchain. - Authentication
- None required for
configscans.
kind: checkov
Checkov is an open-source policy-as-code scanner
maintained by Bridgecrew / Prisma Cloud
(Palo Alto Networks). The checkov kind runs Checkov over the component's
Terraform sources and feeds the SARIF output through Atmos's shared scanner
result handler.
hooks:
policy:
events: [after-terraform-plan]
kind: checkov
command(default)checkov.args(default)-d $ATMOS_COMPONENT_PATH -o sarif --output-file-path $ATMOS_OUTPUT_DIR --quiet --soft-fail. Note: Checkov's--output-file-pathtakes a directory and writesresults_sarif.sarifinside it.- Output
- SARIF file written to
$ATMOS_OUTPUT_DIR/results_sarif.sarif; the kind's result handler reads from that nested path. on_failure(default)warn.- Runtime requirements
- The
checkovbinary on PATH. Declare it independencies.toolsto have Atmos auto-install through the toolchain. - Default env additions
- Atmos sets
SSL_CERT_FILEfrom the host CA bundle when one is detected. The PyInstaller-packaged Checkov release ships a frozencertifibundle that often cannot validate the TLS chain servingapi0.prismacloud.io(the API Checkov hits at startup to fetch guideline mappings); pointingSSL_CERT_FILEat the host bundle is the official PyInstaller workaround. On hosts where Atmos cannot detect a CA bundle, the kind makes no env change and Checkov falls back to its bundledcertifi.
kind: kics
KICS (Keeping Infrastructure as Code Secure) is an
open-source IaC scanner from Checkmarx. The
kics kind runs KICS over the component's Terraform sources and feeds
the SARIF output through Atmos's shared scanner result handler.
hooks:
iac-scan:
events: [after-terraform-plan]
kind: kics
command(default)kics.args(default)scan -p $ATMOS_COMPONENT_PATH -o $ATMOS_OUTPUT_DIR --report-formats sarif --no-progress.- Output
- SARIF file written to
$ATMOS_OUTPUT_DIR/results.sarif. on_failure(default)warn.- Runtime requirements
- The
kicsbinary on PATH. Declare it independencies.toolsto have Atmos auto-install through the toolchain — Atmos ships a curated registry override sokics: "<version>"resolves correctly even though the upstream Aqua entry uses an unsupportedgo_buildtype. KICS_QUERIES_PATH- Required at runtime. The KICS release tarball does not include the query library. Homebrew install:
export KICS_QUERIES_PATH=$(brew --prefix kics)/share/kics/assets/queries. For toolchain installs, point at the unpackedassets/queriesdirectory shipped alongside the release archive.
All three scanner kinds emit SARIF and share a result handler that renders
findings as a compact markdown table — severity, rule ID (linked to the
upstream remediation guide when available), short description, and
file:line location. The same markdown body flows to your terminal, the
Atmos Pro run page, and PR comments.
kind: store
Reads Terraform outputs and writes them to an external store (SSM,
Secrets Manager, Azure Key Vault, etc.). Use this to share state between
components without backend wiring — paired with the !store
function for reading.
hooks:
outputs:
events: [after-terraform-apply]
kind: store
name: prod/ssm
outputs:
vpc_id: .id
private_subnet_ids: .private_subnet_ids
See Store function reference below for configuration details.
kind: command (generic engine)
The escape hatch — runs any toolchain-resolved binary with Atmos's
standard ATMOS_* env-var contract. Use when no built-in kind fits
your tool.
hooks:
custom-scan:
events: [after-terraform-plan]
kind: command
command: my-internal-scanner
args:
- "--component"
- "{{ .atmos_component }}"
- "--source"
- "$ATMOS_COMPONENT_PATH"
- "--out"
- "$ATMOS_OUTPUT_FILE"
format: markdown # optional; tells Atmos to render the output file as markdown
on_failure: warn # warn | fail | ignore
The subprocess receives these environment variables:
ATMOS_COMPONENT_PATH- On-disk path to the component's Terraform module (honors workdir resolution).
ATMOS_PLANFILE- Planfile path on after-plan events (when threaded — currently a stub).
ATMOS_OUTPUT_DIR- A fresh temp directory created by Atmos before the subprocess starts (via
os.MkdirTemp("", "atmos-hook-*")). Unique per hook invocation — two hooks firing on the same event get separate directories and do not share state through these env vars. The directory and everything inside it is deleted automatically when the hook returns. Use this for tools that write to a directory rather than a single file (e.g., KICS, Checkov). ATMOS_OUTPUT_FILE$ATMOS_OUTPUT_DIR/output. Write structured output here for the kind's result handler to read. Same lifetime asATMOS_OUTPUT_DIR.ATMOS_STACK- The stack name.
ATMOS_COMPONENT- The component name.
Atmos creates a fresh temp directory per hook invocation, points
ATMOS_OUTPUT_DIR and ATMOS_OUTPUT_FILE at it, and deletes the entire
directory after the hook returns. Custom commands can write any
additional files they need into $ATMOS_OUTPUT_DIR — sibling hooks won't
collide, and cleanup is automatic.
Tool stdout/stderr stream through Atmos's I/O layer so you see them in real time — same UX as Terraform plan output.
Overriding Kind Defaults
Every named kind (infracost, trivy, checkov, kics, store)
ships defaults for command, args, env, and on_failure. Any
field you set on the hook overrides the kind's default for that field.
args and env are full replacement — if you set args on the
hook, the kind's default arg list is discarded entirely (you must
restate every arg you want). Same for env. If you only need to tweak
one flag, use kind: command directly and pass the full command line.
A common case is bumping a scanner's severity threshold or adding a config file. Because override replaces the entire arg list, copy the default args from the kind's reference section above and add your overrides:
hooks:
security:
events: [after-terraform-plan]
kind: trivy
args:
- config
- --format
- sarif
- --output
- $ATMOS_OUTPUT_FILE
- --severity
- HIGH,CRITICAL # added
- --quiet
- $ATMOS_COMPONENT_PATH
You can also override just on_failure (independent of args/env) to
make a scanner finding block the run:
hooks:
security:
events: [after-terraform-plan]
kind: trivy
on_failure: fail # default is warn
Or override env to inject a tool-specific config path:
hooks:
policy:
events: [after-terraform-plan]
kind: checkov
env:
CHECKOV_CONFIG_FILE: $ATMOS_COMPONENT_PATH/.checkov.yaml
Setting env on the hook replaces the kind's DefaultEnv map
entirely. For kind: checkov specifically, that means losing the
default SSL_CERT_FILE workaround (see kind: checkov)
— restate it if you still need it.
Tool Auto-Install via dependencies.tools
Declare which tools a hook needs and Atmos installs them automatically through the toolchain before the hook fires. Same syntax used by component dependencies:
components:
terraform:
vpc:
dependencies:
tools:
infracost: "0.10.44"
checkov: "3.2.529"
hooks:
cost: { events: [after-terraform-plan], kind: infracost }
security: { events: [after-terraform-plan], kind: checkov }
A pre-flight check runs once per command invocation:
- Resolve every tool the component declares.
- Install any that aren't already present.
- Build a PATH augmentation that points at the installed pinned versions.
- Verify each hook's binary is resolvable.
If any binary is missing, the run aborts before Terraform with a
clear error including a hint to declare it in dependencies.tools. You
won't lose a 90-second plan to find out a hook is misconfigured.
Pin a concrete version ("3.2.529") or use a SemVer constraint
("~> 3.2"). "latest" is accepted but the resulting PATH may not match
what the toolchain installed — pin a version for reliable behavior.
Curated Atmos Registry
Atmos ships a small curated registry baked into the binary that
overrides upstream Aqua registry entries for tools that don't model
cleanly with Aqua's defaults. Today this covers KICS (the upstream
entry uses type: go_build which the Atmos installer doesn't support
yet). The override is invisible to users — you just write kics: "2.1.20" in dependencies.tools and it works.
You can override even the built-in overrides with your own registry entry at higher priority — see Toolchain Configuration for the registry syntax.
Skipping Hooks at Runtime
Set --skip-hooks (or the ATMOS_SKIP_HOOKS environment variable) to
bypass hooks for a single invocation without editing stack config.
Useful for emergency operations, local iteration, or when a scanner is
producing noisy false positives you want to ignore for one run.
atmos terraform plan vpc -s prod --skip-hooks # skip all
atmos terraform plan vpc -s prod --skip-hooks=cost,security # skip specific hooks by name
ATMOS_SKIP_HOOKS=true atmos terraform apply vpc -s prod # via env var
Skipped hooks are logged at INFO level so it stays visible in CI
output. The flag is per-invocation only — it doesn't propagate to
nested commands or workflows. See Global Flags for
all options.
Hooks with --all
atmos terraform plan --all / atmos terraform apply --all walks every
terraform component in the selected stack(s) in dependency order and runs
the command for each. Hooks fire just like a single-component invocation,
with these specifics:
- Hooks fire per component, in dependency order.
destroy --allreverses the order so dependents tear down before their dependencies. - Tool auto-install runs per component. Each component's
dependencies.toolsis resolved on its own, so different components in the same--allrun can pin different versions of the same tool — the toolchain PATH is rebuilt per component. --skip-hookspropagates across the whole run. The flag is set on the shared invocation state and applies uniformly to every component--allvisits.on_failure: failaborts the remaining traversal. A failing hook on component K stops--allbefore it visits any downstream node. Components already processed stay processed — there is no rollback.
Failure Modes
Every hook can declare an on_failure mode:
warn(default)- Log a warning if the hook fails, continue. Built-in scanner kinds default to
warnso a security or cost issue doesn't block your plan unless you explicitly want it to. fail- Propagate the subprocess error — the terraform command aborts. Use when a hook finding should block the operation (e.g., critical security violations).
ignore- Silently swallow failures. Useful for opportunistic notifications you don't want to ever break a run.
Format Symmetry
When a hook's kind produces a markdown summary (every built-in kind does), Atmos renders the same bytes in:
- Your terminal (via
ui.MarkdownMessage) - The Atmos Pro run page (when Pro is connected)
No separate "terminal-friendly" vs "Pro-friendly" variants — write markdown once, get it everywhere.
Backwards Compatibility
The pre-rename command: YAML key is still accepted as an alias for
kind:. Existing stack manifests like:
hooks:
vpc-id:
events: [after-terraform-apply]
command: store # legacy form — still works
name: vpc/id
outputs:
id: .vpc_id
…parse identically post-upgrade. No migration required.
Store Function Reference
For complete details on the store kind (configuring stores, supported
backends, output mapping, etc.), see Sharing State with
Stores.
hooks.<name>- Map key is the hook's name (unique per component).
hooks.<name>.events- List of lifecycle events that trigger this hook.
hooks.<name>.kind- Which engine runs the hook. One of
store,command,infracost,checkov,trivy,kics. Aliased from legacycommand:field. hooks.<name>.command- For
kind: command: the binary to execute (resolved via the toolchain or PATH). Named kinds default this. hooks.<name>.args- For
kind: command: arguments passed to the binary. Supports Go template syntax ({{ .atmos_component }}) for stack metadata and$ATMOS_*env-var substitution at exec time. hooks.<name>.env- Additional env vars exported to the subprocess. Supports the same templating as
args. hooks.<name>.format- Optional rendering hint. v1 supports
markdown— tells Atmos to render the tool's output file as markdown in the terminal. hooks.<name>.on_failurewarn(default),fail, orignore.hooks.<name>.name/hooks.<name>.outputs- Store-kind specific. See store function reference.
Examples
Five working examples live in the Examples gallery:
hooks-infracost— cost analysis withkind: infracosthooks-trivy— security scanning withkind: trivyhooks-checkov— policy scanning withkind: checkovhooks-kics— IaC scanning withkind: kicshooks-custom-command— custom Python script viakind: command
Each uses dummy AWS provider config so tofu plan succeeds offline.
Related
- Global Flags —
--skip-hooksand friends - Component Dependencies —
dependencies.toolssyntax used for auto-install - Toolchain Configuration — registry overrides
- External Stores — reading data written by
kind: store - Terraform State — alternative state-sharing pattern via
!terraform.state