Custom Commands
Atmos can be easily extended to support any number of custom CLI commands. Custom commands are exposed through the atmos CLI when you run atmos help. It's a great way to centralize the way operational tools are run in order to improve DX.
For example, one great way to use custom commands is to tie all the miscellaneous scripts into one consistent CLI interface. Then we can kiss those
ugly, inconsistent arguments to bash scripts goodbye! Just wire up the commands in atmos to call the script. Then developers can just run atmos help
and discover all available commands.
Simple Example
Here is an example to play around with to get started.
Adding the following to atmos.yaml will introduce a new hello command.
# Custom CLI commands
commands:
- name: hello
description: This command says Hello world
steps:
- "echo Hello world!"
We can run this example like this:
atmos hello
Positional Arguments
Atmos also supports positional arguments. If a positional argument is required but not provided by the user, the command will fail—unless you define a default in your config.
For the example, adding the following to atmos.yaml will introduce a new greet command that accepts one name argument,
but uses a default of "John Doe" if none is provided.
# subcommands
commands:
- name: greet
description: This command says hello to the provided name
arguments:
- name: name
description: Name to greet
required: true
default: John Doe
steps:
- "echo Hello {{ .Arguments.name }}!"
We can run this example like this:
atmos greet Alice
or defaulting to "John Doe"
atmos greet
Trailing Arguments
Atmos supports trailing arguments after -- (a standalone double-dash). The -- itself is a delimiter that signals the end of Atmos-specific options. Anything after -- is passed directly to the underlying command without being interpreted by Atmos. The value of these trailing arguments is accessible in {{ .TrailingArgs }}.
For the example, adding the following to atmos.yaml will introduce a new echo command that accepts one name argument and also uses trailingArgs
- name: ansible run
description: "Runs an Ansible playbook, allowing extra arguments after --."
arguments:
- name: playbook
description: "The Ansible playbook to run"
default: site.yml
required: true
steps:
- "ansible-playbook {{ .Arguments.playbook }} {{ .TrailingArgs }}"
Output:
$ atmos ansible run -- --limit web
Running: ansible-playbook site.yml --limit web
PLAY [web] *********************************************************************
Passing Flags
Passing flags works much like passing positional arguments, except for that they are passed using long or short flags.
Flags can be optional (this is configured by setting the required attribute to false).
# subcommands
commands:
- name: hello
description: This command says hello to the provided name
flags:
- name: name
shorthand: n
description: Name to greet
required: true
steps:
- "echo Hello {{ .Flags.name }}!"
We can run this example like this, using the long flag:
atmos hello --name world
Or, using the shorthand, we can just write:
atmos hello -n world
Boolean Flags
Flags can be defined as boolean type using type: bool. Boolean flags don't require a value to be passed—when present, they are set to true.
commands:
- name: deploy
description: Deploy to environment
flags:
- name: dry-run
shorthand: d
description: Perform a dry run without making changes
type: bool
- name: verbose
shorthand: v
description: Enable verbose output
type: bool
default: false
- name: auto-approve
description: Auto-approve without prompting
type: bool
default: true
steps:
- |
{{ if .Flags.dry-run }}
echo "DRY RUN MODE"
{{ end }}
{{ if .Flags.verbose }}
echo "Verbose output enabled"
{{ end }}
{{ if .Flags.auto-approve }}
terraform apply -auto-approve
{{ else }}
terraform apply
{{ end }}
Usage:
# Enable dry-run (sets it to true)
atmos deploy --dry-run
# Use short flag
atmos deploy -d
# Boolean with explicit value
atmos deploy --auto-approve=false
Using Boolean Flags in Steps
Boolean flags are available as Go template variables with values true or false. Here are common patterns for using them in bash:
commands:
- name: build
description: Build the project
flags:
- name: verbose
shorthand: v
type: bool
description: Enable verbose output
- name: clean
type: bool
default: true
description: Clean before building
steps:
# Pattern 1: Conditional command execution with if/else
- |
{{ if .Flags.verbose }}
echo "Verbose mode enabled"
set -x
{{ end }}
# Pattern 2: Inline conditional flag
- echo "Building{{ if .Flags.verbose }} with verbose output{{ end }}..."
# Pattern 3: Pass as flag to another command
- make build {{ if .Flags.verbose }}VERBOSE=1{{ end }}
# Pattern 4: Conditional step execution
- |
{{ if .Flags.clean }}
echo "Cleaning build directory..."
rm -rf ./build
{{ end }}
# Pattern 5: Negation check
- |
{{ if not .Flags.clean }}
echo "Skipping clean step"
{{ end }}
# Pattern 6: Convert to shell variable
- |
VERBOSE={{ .Flags.verbose }}
if [ "$VERBOSE" = "true" ]; then
echo "Verbose is on"
fi
# Pattern 7: Using printf for explicit string conversion
- |
VERBOSE={{ printf "%t" .Flags.verbose }}
echo "Verbose flag is: $VERBOSE"
Boolean values automatically render as true or false (lowercase strings) when used in templates. You can reference them directly with {{ .Flags.name }}—no conversion needed!
Flag Defaults
Both string and boolean flags support default values using the default attribute:
flags:
- name: environment
description: Target environment
default: "development"
- name: force
type: bool
description: Force the operation
default: false
- name: auto-approve
type: bool
description: Skip confirmation prompts
default: true
When a flag has a default value, users can omit it from the command line. The default value will be used unless explicitly overridden.