steps
The steps field is the work a custom command performs. Custom commands run steps through the same engine as workflows and support the same step types — from plain shell and Atmos commands to interactive prompts, spinners, tables, and container operations.
Simple and structured steps
In its simplest form, steps is a list of shell command strings:
commands:
- name: hello
description: Say hello
steps:
- "echo Hello world!"
- "echo Goodbye!"
For anything beyond a plain shell command, use the structured form — an object per step with a
type and the fields for that type. The two forms can be mixed in the same list:
commands:
- name: deploy
description: Deploy with a confirmation prompt
steps:
- type: confirm
name: proceed
prompt: "Deploy to production?"
default: false
- "echo Deploying..."
- type: atmos
command: terraform apply vpc -s plat-ue2-prod
A structured step is the same object documented for workflows. See the
step reference for the common step fields (name, type, command,
output, and more).
Conditional steps
Structured custom command steps support the same when condition syntax as
workflow steps. Omitted when means the step runs while the command status is
still success. After a step fails, Atmos keeps evaluating later structured
steps so when: failure and when: always steps can run. A false condition
skips the step without failing the command. Atmos evaluates built-in predicate
keywords first; any other scalar string is evaluated as a CEL expression. Use
!cel when you want to make that explicit.
commands:
- name: release
description: Run release checks
steps:
- type: shell
command: ./scripts/ci-release-checks.sh
when: ci
- type: shell
command: ./scripts/local-summary.sh
when:
not: ci
- type: shell
command: ./scripts/prod-only.sh
when: !cel 'stack == "prod" && ci'
String shorthand steps remain plain shell commands and always run. Use the structured form when a custom command step needs a condition.
Use when: always for cleanup that must run even when an earlier step fails:
commands:
- name: preview
description: Start a local service, run a preview, then tear it down
steps:
- type: shell
name: start-emulator
command: atmos emulator up aws -s dev --ephemeral
- type: shell
name: run-preview
command: atmos terraform apply demo -s dev -auto-approve
- type: shell
name: stop-emulator
when: always
command: atmos emulator down aws -s dev
- type: shell
name: validate
command: atmos terraform output demo -s dev
If run-preview fails, stop-emulator still runs and validate is skipped
because its omitted when is equivalent to success-only. The command still
returns the original preview failure; cleanup does not hide it.
Step predicates are:
ci- Run only when Atmos detects CI using its standard CI detection.
local- Run only outside CI.
always- Run after success or failure. Use this for teardown and cleanup steps.
never- Never run.
success- Run when the step condition is evaluated with success status.
failure- Run only after an earlier step has failed.
CEL expressions can read ci, status, stack, component, workflow,
step, and env. For workflow and custom command steps, status is the
current command status: success before any step fails and failure after an
earlier step failed.
Step types
Custom commands and workflows share one step-type registry, so every step type works the same in both. The full reference lives with the workflow docs — see the step type reference for each type's fields, examples, and behavior.
Atmos supports 25+ step types for workflows and custom command steps. Step type values live in the type field on a step object; type-specific fields stay on that same step object.
Step Types Overview
| Category | Step Types | Description |
|---|---|---|
| Command | atmos, shell, exec, container, emulator, http, archive | Run Atmos, shell, process-replacement, container, or emulator-lifecycle operations, call an HTTP endpoint, or pack/update a zip/tar/tgz archive |
| Orchestration | parallel, matrix, wait, wait-all, cancel | Run child steps concurrently, expand across a matrix, or wait for and tear down background container services |
| Interactive | input, confirm, choose, filter, file, write | Collect user input in a TTY |
| UI | toast, markdown, alert, say, title, clear, linebreak, stage, sleep, env, exit | Display status, control terminal output, or influence workflow control flow |
| Output | spin, table, pager, format, join, style, log | Format, render, log, or capture output |
Status Messages
Use type: toast for themed status messages. success, info, warn, and error are level values, not step types:
steps:
- name: done
type: toast
level: success
content: Deployment complete.
Interactive Steps
Interactive step types require a TTY. In CI, avoid prompts by using non-interactive command steps, defaults, or values supplied through environment variables.
Full Reference
See the step type reference for the complete list, examples, required fields, and type-specific behavior.
Interactive and TTY Steps
Some commands need to take over the terminal — SSM sessions, SSH, database consoles, vim, top. By default, shell steps run with Atmos managing output (masking, capture), which mangles full-screen terminal applications and means Ctrl-C interrupts Atmos itself instead of the command.
Steps support docker-style tty and interactive fields to hand the terminal to the command:
commands:
- name: ssh
description: Open an SSM session to an instance
arguments:
- name: instance_id
description: EC2 instance ID
required: true
steps:
- type: shell
tty: true
interactive: true
command: "aws ssm start-session --target {{ .Arguments.instance_id }}"
With this configuration, atmos ssh i-1234567890 behaves like docker run -it: the session gets a real TTY, keystrokes (including Ctrl-C) go to the session instead of Atmos, and the session's exit code becomes the Atmos exit code.
interactiveAttach host stdin to the step and let the step handle Ctrl-C (like
docker -i). While the step runs, Atmos suspends its own interrupt handling so pressing Ctrl-C interrupts the step's process, not Atmos. Output is still masked and captured normally.ttyAllocate a pseudo-terminal for the step (like
docker -t). The command sees a real TTY on stdin/stdout, so full-screen and cursor-addressing programs render correctly. Secret masking is still applied to session output. The step'soutputmode does not apply (the session writes directly to your terminal), and the step produces no capturable output for later steps. Combine withinteractive: truefor full terminal sessions —tty: truealone does not forward your keystrokes.
Pseudo-terminals are supported on macOS and Linux. On Windows, tty: true falls back
to attaching the real console streams directly; the session still works, but secret
masking is unavailable in that mode (Atmos prints a warning when masking is enabled).
The same fields work in workflow shell steps.
Replacing the Atmos Process (type: exec)
For commands that should fully take over — where Atmos is purely a launcher — use a
step of type: exec. The command replaces the Atmos process entirely (shell
exec semantics): on macOS and Linux it's a true execve of the system shell, so
the command inherits your terminal, environment, and working directory natively,
job control (Ctrl-Z) works exactly as if you'd run the command yourself, and its
exit code becomes the Atmos exit code with no intermediary.
commands:
- name: ssh
description: Open an SSM session to an instance
arguments:
- name: instance_id
description: EC2 instance ID
required: true
steps:
- type: exec
command: "aws ssm start-session --target {{ .Arguments.instance_id }}"
Choose between the two models:
tty: true+interactive: true— Atmos as supervisorSecret masking applied to session output, multiple steps before and after the session, retries and timeouts available. The session runs under a pseudo-terminal proxied by Atmos.
type: exec— Atmos as launcherNative terminal behavior with zero proxy overhead. Because the process is replaced: the exec step must be the last step (validated — later steps could never run), no secret masking applies to its output, no steps run after it, and supervisor-only fields (
tty,interactive,retry,timeout,output) are rejected.ATMOS_SHLVLis not incremented (the session replaces Atmos rather than nesting under it). Tools likeaws ssm start-sessionbehave exactly as when run directly.
On Windows, which has no execve, type: exec is emulated: the command runs with
the real console streams attached, Atmos waits while the command owns Ctrl-C, and
the command's exit code is propagated as the Atmos exit code.
Extended Step Types
Beyond shell, atmos, and exec, custom commands support the full set of extended step types —
the same ones workflows use — enabling interactive CLI wizards directly in your custom commands.
Use these step types to collect user input, display formatted output, and control execution flow.
Running Interactive Steps in CI (non-TTY)
Interactive step types (choose, input, confirm, filter, file, write) need a terminal to
prompt. When a custom command runs without a TTY — most commonly in CI — each interactive step uses
its default value instead of prompting. A step without a default fails with
interactive terminal required for step, so an unattended run never proceeds with an unintended
value.
Source the default from the environment with the !env function (!env VAR fallback) so the same
command is interactive locally and non-interactive in CI:
commands:
- name: deploy
description: Deploy to an account
steps:
- name: account
type: choose
prompt: "Account"
options: [dev, prod]
default: !env STACK_ACCOUNT dev
- type: shell
command: 'echo "Deploying {{ .steps.account.value }}"'
Locally this prompts; in CI it reads STACK_ACCOUNT (falling back to dev) and passes the value to
the shell step. The same behavior applies to
workflow steps.
Example: Interactive Deployment Command
commands:
- name: deploy-wizard
description: Interactive deployment wizard
steps:
# Collect environment selection
- name: env
type: choose
prompt: "Select target environment"
options:
- dev
- staging
- prod
default: dev
# Show warning for production
- name: prod_warning
type: toast
level: warn
content: "You are about to deploy to PRODUCTION!"
# Confirm deployment
- name: confirm
type: confirm
prompt: "Deploy to {{ .steps.env.value }}?"
default: false
# Run the deployment
- name: deploy
type: atmos
command: terraform apply vpc -s {{ .steps.env.value }}
# Show success message
- name: done
type: toast
level: success
content: "Deployment to {{ .steps.env.value }} completed!"
Example: Multi-Component Deploy with Selection
commands:
- name: deploy-components
description: Select and deploy multiple components
steps:
# Multi-select components
- name: components
type: filter
prompt: "Select components to deploy"
multiple: true
options:
- vpc
- eks
- rds
- s3
- lambda
# Show selected components
- name: summary
type: markdown
content: |
## Deployment Summary
You selected **{{ len .steps.components.values }}** components:
{{ range .steps.components.values }}
- {{ . }}
{{ end }}
# Confirm and deploy
- name: confirm
type: confirm
prompt: "Proceed with deployment?"
# Deploy each component
- type: shell
command: |
{{ range .steps.components.values }}
echo "Deploying {{ . }}..."
{{ end }}
Example: Input Collection
commands:
- name: create-ticket
description: Create a deployment ticket
steps:
- name: title
type: input
prompt: "Ticket title"
placeholder: "Brief description of the change"
- name: description
type: write
prompt: "Detailed description"
- name: priority
type: choose
prompt: "Priority level"
options: [low, medium, high, critical]
default: medium
- type: toast
level: success
content: |
Ticket created:
Title: {{ .steps.title.value }}
Priority: {{ .steps.priority.value }}