Status: ✅ Implemented (v1 shipped in v1.210.0; v2 surface added in v1.211.0 — see "v2 Surface" section below)
Last Updated: 2026-05-05
Related PRDs: Tool Dependencies Integration
The dependencies.components section in stack configurations defines relationships between Atmos components. It enables:
- Execution order control (deploy VPC before subnets)
- CI/CD orchestration (Spacelift/Atlantis dependency ordering)
- Impact analysis (
atmos describe affecteddetects changes) - File/folder monitoring (trigger redeployments when external files change)
// ComponentDependency represents a dependency entry in dependencies.components.
type ComponentDependency struct {
// Component instance name (required for component-type dependencies).
// This is the name under components.<kind>.<name>, not the Terraform module path.
Component string `yaml:"component,omitempty" json:"component,omitempty" mapstructure:"component"`
// Stack name (optional, defaults to current stack). Supports Go templates.
Stack string `yaml:"stack,omitempty" json:"stack,omitempty" mapstructure:"stack"`
// Kind specifies the dependency type: terraform, helmfile, packer, file, folder, or plugin type.
// Defaults to the declaring component's type for component dependencies.
Kind string `yaml:"kind,omitempty" json:"kind,omitempty" mapstructure:"kind"`
// Path for file or folder dependencies (required when kind is "file" or "folder").
Path string `yaml:"path,omitempty" json:"path,omitempty" mapstructure:"path"`
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
component |
string | Yes* | - | Component instance name (under components.<kind>.<name>) |
stack |
string | No | current stack | Target stack (supports templates) |
kind |
string | No | declaring component's type | terraform, helmfile, packer, file, folder, or registered plugin type |
path |
string | Yes** | - | Path for file or folder kind dependencies |
*Required when kind is a component type (terraform, helmfile, packer, plugin).
**Required when kind is file or folder.
| Kind | Description | Required Fields |
|---|---|---|
terraform |
Terraform component dependency | component, optional stack |
helmfile |
Helmfile component dependency | component, optional stack |
packer |
Packer component dependency | component, optional stack |
file |
File path dependency | path |
folder |
Folder path dependency | path |
<plugin> |
Custom plugin component type | component, optional stack |
components:
terraform:
subnet:
dependencies:
components:
- component: vpc # kind defaults to "terraform"
- component: security-groupcomponents:
terraform:
app:
dependencies:
components:
- component: vpc
- component: shared-vpc
stack: acme-ue1-network
- component: rds
stack: "{{ .vars.tenant }}-{{ .vars.environment }}-prod"components:
terraform:
app:
dependencies:
components:
- component: vpc # terraform (default)
- component: nginx-ingress
kind: helmfile # helmfile component
stack: platform-stack
- component: base-ami
kind: packercomponents:
terraform:
lambda:
dependencies:
components:
- component: vpc
- kind: file
path: configs/lambda-settings.json
- kind: folder
path: src/lambda/handlerWhen kind is omitted on a component dependency:
- A terraform component's dependencies default to
kind: terraform - A helmfile component's dependencies default to
kind: helmfile - A packer component's dependencies default to
kind: packer
dependencies.components uses append merge behavior when list_merge_strategy: append is configured in atmos.yaml. Child stacks add their dependencies to parent dependencies.
# Parent stack
dependencies:
components:
- component: account-settings
# Child stack (inherits parent)
components:
terraform:
vpc:
dependencies:
components:
- component: network-baseline # APPENDED to parent's dependencies
# Result: vpc depends on both account-settings AND network-baseline-
For component dependencies (
kindis terraform, helmfile, packer, or plugin type):componentfield is requiredpathfield is ignored
-
For path dependencies (
kind: fileorkind: folder):pathfield is requiredcomponentfield is ignored
The ComponentDependency struct provides helper methods:
// IsFileDependency returns true if this is a file dependency.
func (d *ComponentDependency) IsFileDependency() bool {
return d.Kind == "file"
}
// IsFolderDependency returns true if this is a folder dependency.
func (d *ComponentDependency) IsFolderDependency() bool {
return d.Kind == "folder"
}
// IsComponentDependency returns true if this is a component dependency (not file or folder).
func (d *ComponentDependency) IsComponentDependency() bool {
return d.Kind != "file" && d.Kind != "folder"
}The getComponentDependencies() function:
- Checks
dependencies.componentsfirst (preferred location) - Falls back to
settings.depends_on(legacy location) - Returns dependencies with source indicator for matching logic
The getFileFolderDependencies() function:
- Filters dependencies by
IsFileDependency()orIsFolderDependency() - Uses
dep.Pathfor the file/folder location - Supports legacy
file/folderfields for backward compatibility.
The legacy settings.depends_on format continues to work. When migrating:
| Old Format | New Format |
|---|---|
settings.depends_on |
dependencies.components |
| Map with numeric keys | List |
namespace, tenant, environment, stage fields |
stack field with templates |
file and folder fields |
kind: file or kind: folder with path field |
Before:
settings:
depends_on:
1:
component: vpc
2:
component: rds
stage: prod
3:
file: configs/app.jsonAfter:
dependencies:
components:
- component: vpc
- component: rds
stack: "{{ .vars.tenant }}-{{ .vars.environment }}-prod"
- kind: file
path: configs/app.jsonThe original v1 shape that shipped in v1.210.0 layered three design smells:
- Container/contents mismatch. The container is named
dependencies.components(a noun for the category) but its entries can bekind: file/kind: folder— files and folders are not components. - Two entry shapes mashed together. Component entries use a value-bearing key (
component: vpc); path entries use a discriminator pattern (kind: file+path:). Half-discriminated, half-typed-by-key. kindoverload.kindatcomponents.<kind>.<name>means component type (terraform/helmfile/packer).kindinside a dependency entry addsfile/folder. Same word, overlapping but different domains.
Hard renames are off the table — the v1 surface ships in a public release. The v2 surface is purely additive and reconciles the smells without breaking any existing YAML.
dependencies:
tools: # unchanged (map of tool → version)
terraform: "1.9.8"
components: # ONLY component-to-component deps
- name: vpc # canonical (preferred over `component:`)
stack: prod
- name: nginx
kind: helmfile
stack: platform-stack
files: # NEW sibling key, replaces inline `kind: file`
- configs/lambda-settings.json
folders: # NEW sibling key, replaces inline `kind: folder`
- src/lambda/handlercomponent:continues to parse as the canonical struct field onComponentDependency. The newname:field is an input-side alias.kind: file/kind: folderentries insidedependencies.components[]continue to parse and behave identically.settings.depends_onlegacy path is unchanged.
After mapstructure decoding of any dependencies section, callers MUST invoke Dependencies.Normalize. The normalizer:
- Resolves the
name↔componentalias on everyComponents[]entry. If both are set to the same non-empty value, the alias is cleared. If both are set to different non-empty values, returnsschema.ErrComponentDependencyNameConflict. - Validates inline path-based entries. Any
Components[i]withKind∈ {file,folder} that lacksPathreturnsschema.ErrComponentDependencyMissingPath. - Mirrors
Files/FoldersintoComponents[]as synthetic entries{Kind: "file"|"folder", Path: ...}. Downstream code paths that filterComponents[]by kind continue to see all path-based dependencies regardless of where they were declared. - Backfills typed slices by promoting any inline file/folder entries from
Components[]intoFiles/Folders. After Normalize, both views are complete and consistent (deduplicated by path).
Net effect: a single internal representation exists post-Normalize, and downstream code paths (getFileFolderDependencies, getComponentDependencies, isComponentDependentFolderOrFileChangedIndexed) work unchanged.
Defined in pkg/schema/dependencies.go (locally, to avoid an errors → pkg/perf → pkg/schema import cycle):
schema.ErrComponentDependencyNameConflict— bothnameandcomponentset to different values on the same entry.schema.ErrComponentDependencyMissingPath— inlinekind: file/folderentry without apath:field.
The JSON manifest schema (website/static/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json and its mirrored copy under tests/fixtures/schemas/) gains:
dependencies.files/dependencies.folderskeys withdependencies_files/dependencies_foldersdefinitions.dependencies_component_entry.properties.namealongsidecomponent.- An updated
anyOfacceptingname,component, OR the legacykind: file/folder + pathshape.
additionalProperties: false is preserved; the schema still rejects unknown keys.
Tracked separately:
- Decide whether/when to emit a soft-deprecation log when
kind: file/folderis seen insidecomponents[]. - Consider a v2 schema namespace if more shape changes accumulate.
- Same alias treatment for legacy
settings.depends_onis intentionally NOT in scope.
- Component Dependencies User Guide —
website/docs/stacks/dependencies/components.mdx - describe dependents —
website/docs/cli/commands/describe/dependents.mdx - describe affected —
website/docs/cli/commands/describe/affected.mdx - Tool Dependencies Integration