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.
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.