atmos scaffold
Give your team reusable templates for components, configuration, and projects. Scaffolds generate consistent files from validated answers and can merge later template improvements into previously generated output.
Commands
| Command | Purpose |
|---|---|
atmos scaffold list | List embedded, configured, and catalog templates. |
atmos scaffold generate | Generate or update output from a template. |
atmos scaffold validate | Validate a template's scaffold.yaml manifest. |
Template Contract
Each template directory contains a versioned scaffold.yaml plus files to generate. Files are
discovered automatically; use spec.files only to conditionally gate discovered files.
apiVersion: atmos/v1
kind: AtmosScaffoldConfig
metadata:
name: terraform-component
description: Standard Terraform component
spec:
fields:
- name: component_name
label: Component name
type: input
required: true
validation:
pattern: "^[a-z0-9-]+$"
message: Use lowercase letters, digits, and hyphens.
- name: environments
label: Environments
type: multiselect
options: [dev, staging, prod]
default: [dev]
spec.fields replaces the retired prompts: shape. Fields are available to rendered files as
{{ .Config.component_name }}. Atmos validates declared fields after it merges interactive input,
defaults, persisted spec.values, and repeated --set key=value flags, so automation follows the
same rules as an interactive run.
Supported field types are input, text, string, select, multiselect, confirm, bool,
boolean, and computed. Required fields reject missing or blank text and empty multiselect
values; false is a valid boolean answer. Select values must come from options, and text fields
can use validation.pattern with an optional validation.message. A computed field is never
prompted for — see Computed Fields in the
generate reference.
Conditions and Files
Use when: to make one template adapt to its answers. A condition can be always, never,
ci, or local; a CEL expression; or a list treated as an implicit logical all. A field can
only read answers from fields declared before it.
spec:
fields:
- name: enable_monitoring
type: confirm
default: false
- name: alert_email
type: input
when: "answers.enable_monitoring == true"
files:
- path: monitoring/alerts.tf
when: "answers.enable_monitoring == true"
- path: stacks/staging.yaml
when: "'staging' in answers.environments"
Use CEL operators such as &&, ||, and ! for compound conditions. The map form
{all: ...}, {any: ...}, or {not: ...} is not accepted in scaffold manifests.
Template Delimiters
Template files use {{ }} by default. A template that also generates files with template syntax
of their own, such as a Helm chart or a GitHub Actions workflow, can choose different delimiters
so those expressions pass through untouched. Set a template-wide pair with spec.delimiters, and
override it for specific files with delimiters on a spec.files entry. Each pair is exactly two
non-empty strings: the left and the right delimiter.
spec:
delimiters: ["{{", "}}"] # template-wide default (optional)
files:
- path: "charts/**" # Helm templates use {{ }} themselves
delimiters: ["[[", "]]"]
- path: ".github/workflows/*.yml.tmpl"
delimiters: ["<<", ">>"] # keeps ${{ github.sha }} literal
With this manifest, a chart template can mix both syntaxes. Atmos fills in the [[ ]] expression
and leaves Helm's {{ }} expression for Helm to render later:
# atmos:template
name: [[ .Config.name ]]
image: "{{ .Values.image }}"
The delimiters for a file are chosen in this order:
- The
delimitersof the matchingspec.filesentry. When several entries match the same file, the last matching entry wins, the same rule aspathandwhen. spec.delimiters.{{and}}.
For atmos init, the delimiters the command passes in take the place of spec.delimiters.
A file's delimiters apply to everything Atmos renders for that file: its content, its path, its
target, and any matrix axis written as a Go template expression. The when condition is a CEL
expression and never uses delimiters. Only templated files are rendered, which means files ending
in .tmpl or marked with an atmos:template comment; every other file is copied as written. The
generated README is not a spec.files entry and always uses spec.delimiters.
Generation Hooks
Scaffold hooks run around generation at before.scaffold.generate and
after.scaffold.generate. They use the shared hook envelope but intentionally support only
kind: step and kind: steps because generation has no stack/component context.
spec:
hooks:
format:
events: [after.scaffold.generate]
kind: step
type: shell
with:
command: terraform fmt -recursive
verify:
events: [after.scaffold.generate]
kind: steps
when: "answers.enable_monitoring == true"
with:
- type: require
tools: [terraform]
- type: shell
command: terraform validate
For kind: step, type: selects one registered step type and with:
is its configuration. For kind: steps, with: is an ordered step list. The envelope owns
events, when, env, retry, and on_failure; answer values are available in conditions as
answers and in step templates as {{ .Answers.<field> }}. Hooks run in stable name order.
A step's working_directory: defaults to (or, if bare-relative, resolves under) the scaffold's
target directory, also exposed as {{ .TargetPath }}, so the format/verify hooks above run
against the generated project. Set working_directory: "." to run a step in the directory
atmos was launched from instead. A type: atmos step is exempt from this default -- it keeps
running in the directory atmos was launched from when working_directory: is unset, since the
nested atmos invocation must resolve its own config there, but an explicit working_directory:
on that step is still honored.
Use --skip-hooks or --skip-hooks=name1,name2 when inspecting an untrusted template or
diagnosing generation. See lifecycle hooks for the broader stack-hook kinds and
the shared step bridge.
Update Existing Output
atmos scaffold generate terraform-component ./components/terraform/vpc
atmos scaffold generate terraform-component ./components/terraform/vpc --update
atmos scaffold generate terraform-component ./components/terraform/vpc --update --merge-strategy=theirs
atmos scaffold generate terraform-component ./components/terraform/vpc --update --update-strategy=rendered
--update uses the recorded base revision to perform an optimistic three-way merge. The default
manual strategy surfaces real conflicts; ours keeps local changes and theirs applies the
template side of a conflict.
--update-strategy controls where the merge's base comes from, independently of --merge-strategy.
tracked (the default) reads it from the target's own Git history. rendered instead re-renders
the template at the ref that produced what's currently on disk, using that generation's recorded
answers, with no Git history dependency at all. This needs two separate things, not one: the
template itself must define a scaffold.yaml manifest (so the old ref's fields can be resolved
when re-rendering), and the target must already carry a prior generation's .atmos/scaffold.yaml
record (a different file, at a different path — it stores that generation's recorded answers, not
the template's field definitions).
--max-changes (default 50) caps the percentage of changed lines a three-way merge is allowed
to touch before --update fails outright instead of applying it. 0 disables this check
entirely (guaranteed to never fail); any other value is compared against a computed percentage
with no upper bound, so raising it only makes a hard failure less likely, never impossible.
Project Initialization
atmos init consumes the same manifest and generation engine for complete
project templates. Use init to select a built-in or catalog project starting point; use
scaffold generate to distribute an organization-specific component, configuration, or other
golden path.