# Configure Component Metadata

The `metadata` section configures how Atmos interprets and manages a component. Most `metadata` fields are only valid within component definitions (`components.terraform.<name>.metadata`). A restricted subset of fields (`labels`, `tags`, `custom`, `enabled`, `locked`, `terraform_workspace_pattern`) can also be set stack-wide as a default.

## Use Cases

- **Component Mapping:** Specify which Terraform root module a component uses.
- **Human-Readable Descriptions:** Document what a component is for.
- **Inheritance:** Define which components a component inherits from.
- **Abstract Components:** Mark components as templates that cannot be deployed directly.
- **Component Control:** Enable, disable, or lock components.

## Configuration Scope

Most `metadata` fields are **only valid inside component definitions**:

```yaml
components:
  terraform:
    vpc:
      metadata:
        component: vpc/network
        inherits:
          - vpc/defaults
```

Component-identity fields — `component`, `inherits`, `type`, `name`, `terraform_workspace` — cannot be set at the global level or under `terraform:` / `helmfile:` / `packer:` defaults. Setting one of these fields at global scope is a hard error, since applying the same component path or inheritance chain to every component in a stack would be a misconfiguration, not a useful default.

### Global (stack-wide) metadata

A restricted allowlist of `metadata` fields — the ones that are data bags or simple gates rather than component identity — can be set at the root of a stack manifest (for example in `_defaults.yaml`), and are deep-merged as a **default** into every component's own `metadata`:

```yaml title="_defaults.yaml"
metadata:
  labels:
    org: acme
  enabled: true
```

Allowed at global scope: `labels`, `tags`, `custom`, `enabled`, `locked`, `terraform_workspace_pattern`. Any other field (`component`, `inherits`, `type`, `name`, `terraform_workspace`) is rejected with an error if set here.

Precedence, lowest to highest:

1. Global `metadata:` (stack-wide default)
2. The `metadata.inherits` base-component chain
3. The component's own local `metadata:` block — always wins

```yaml title="_defaults.yaml"
metadata:
  labels:
    org: acme
```

```yaml title="stacks/catalog/vpc.yaml"
components:
  terraform:
    vpc:
      metadata:
        labels:
          org: platform-team # overrides the global "org: acme" for this component
      vars:
        tags: !labels
```

Here `vars.tags` resolves to `{org: platform-team}` for the `vpc` component, while any component that doesn't set its own `metadata.labels.org` gets `{org: acme}` from the global default.

## Metadata Fields

### `component`

Specifies the path to the Terraform root module, relative to your components directory:

```yaml
components:
  terraform:
    vpc-prod:
      metadata:
        component: vpc # Uses components/terraform/vpc
        description: Production VPC
      vars:
        environment: prod
```

This allows multiple stack components to share the same Terraform root module with different configurations.

### `description`

Provides a human-readable description of the component:

```yaml
components:
  terraform:
    vpc:
      metadata:
        component: vpc
        description: "Virtual Private Cloud with public and private subnets"
```

Atmos preserves `description` as component metadata. It does not change how the component is processed, planned, or applied.

### `name`

Provides a stable logical identity for the component, used to generate the backend `workspace_key_prefix`. This is especially important when using [versioned component folders](/design-patterns/version-management/folder-based-versioning):

```yaml
components:
  terraform:
    vpc:
      metadata:
        name: vpc # Stable logical identity
        component: vpc/v2 # Physical version path
```

**Why this matters:** Without `name`, the `workspace_key_prefix` is auto-generated from `component`, which includes the version (`vpc-v2`). When you upgrade to `vpc/v3`, the workspace key prefix changes, creating a new state file and losing your existing infrastructure state.

**With `metadata.name`:** The workspace key prefix stays stable (`vpc`) across version upgrades, so your Terraform state path remains `vpc/{workspace}/terraform.tfstate` regardless of which version you're running.

See [Workspace Key Management](/design-patterns/version-management/folder-based-versioning#workspace-key-management) for more details.

### `inherits`

Defines a list of components from which this component inherits configuration:

```yaml
components:
  terraform:
    vpc/defaults:
      metadata:
        type: abstract
      vars:
        enable_dns_hostnames: true
        enable_dns_support: true

    vpc-prod:
      metadata:
        inherits:
          - vpc/defaults
      vars:
        vpc_cidr: "10.0.0.0/16"
```

Inheritance is processed in order, with later items and the component's own values taking precedence. For detailed inheritance behavior, see [Inheritance](/howto/inheritance).

### `type`

Marks a component as `abstract` or `real` (default):

```yaml
components:
  terraform:
    vpc/base:
      metadata:
        type: abstract # Cannot be deployed directly
      vars:
        enable_dns_hostnames: true
```

**Abstract components:**

- Serve as templates for other components to inherit from
- Cannot be deployed with `atmos terraform apply`
- Do not appear in `atmos describe stacks` output by default
- Are useful for DRY configuration patterns

### `enabled`

Controls whether a component is active:

```yaml
components:
  terraform:
    monitoring:
      metadata:
        enabled: false # Component is disabled
      vars:
        # ...
```

When `enabled: false`:

- The component is skipped during `atmos terraform apply`
- The component does not appear in active stack listings
- Useful for conditionally disabling components per environment

`enabled` can also be set as a [global default](#global-stack-wide-metadata) to disable every component in a stack unless a component overrides it.

### `locked`

Prevents modifications to a component:

```yaml
components:
  terraform:
    core-network:
      metadata:
        locked: true # Prevent changes
      vars:
        # ...
```

When `locked: true`:

- Atmos will warn or prevent changes to the component
- Useful for protecting critical infrastructure components

`locked` can also be set as a [global default](#global-stack-wide-metadata) to lock every component in a stack unless a component overrides it.

### `terraform_workspace`

Overrides the Terraform workspace name with a literal string value:

```yaml
components:
  terraform:
    vpc:
      metadata:
        terraform_workspace: "custom-workspace-name"
```

By default, Atmos calculates workspace names automatically based on the stack name. Use this field when you need explicit control over the workspace name.

For detailed information about how Atmos manages Terraform workspaces, see [Workspaces](/components/terraform/workspaces).

### `terraform_workspace_pattern`

Overrides the Terraform workspace name using a pattern with context tokens:

```yaml
components:
  terraform:
    vpc:
      metadata:
        terraform_workspace_pattern: "{tenant}-{environment}-{stage}"
```

`terraform_workspace_pattern` can also be set as a [global default](#global-stack-wide-metadata), unlike `terraform_workspace` (an explicit value, which would collide across components if applied stack-wide and so is component-only).

Supported tokens:

- `{namespace}` - The namespace from context variables
- `{tenant}` - The tenant from context variables
- `{environment}` - The environment from context variables
- `{region}` - The region from context variables
- `{stage}` - The stage from context variables
- `{attributes}` - The attributes from context variables
- `{component}` - The Atmos component name
- `{base-component}` - The base component name (from `metadata.component`)

For detailed information about workspace patterns and examples, see [Workspaces](/components/terraform/workspaces).

### `custom`

A user extension point for storing arbitrary metadata that Atmos preserves but does not interpret:

```yaml
components:
  terraform:
    vpc:
      metadata:
        custom:
          owner: platform-team
          cost_center: "12345"
          tier: critical
```

Use `custom` for:

- Storing metadata for external tooling to consume (CI/CD pipelines, dashboards)
- Adding labels or annotations readable via `atmos describe stacks`
- Custom categorization that doesn't affect Atmos behavior

:::note
The `custom` section is inherited from base components when `stacks.inherit.metadata` is enabled (the default). Like other metadata fields, values from derived components override inherited values, and nested maps are deep-merged. `custom` can also be set as a [global default](#global-stack-wide-metadata).
:::

### `tags`

A list of tags used to select this component with the `--tags` flag on commands like [`atmos list components`](/cli/commands/list/components), `atmos terraform plan/apply/deploy`, and the native Kubernetes/Helm/container commands:

```yaml
components:
  terraform:
    vpc:
      metadata:
        tags:
          - production
          - tier-1
```

`--tags` matches **any** of the given tags (OR semantics). For example, `atmos terraform apply --tags production,tier-1` applies components tagged `production` **or** `tier-1`.

`tags` can also be set as a [global default](#global-stack-wide-metadata).

### `labels`

A map of `key: value` labels used to select this component with the `--labels` flag, similar to a Kubernetes label selector:

```yaml
components:
  terraform:
    vpc:
      metadata:
        labels:
          cost-center: platform
          compliance: sox
```

`--labels` matches **all** of the given `key=value` pairs (AND semantics). For example, `atmos terraform apply --labels cost-center=platform,compliance=sox` only applies components whose `metadata.labels` contain both `cost-center: platform` **and** `compliance: sox`.

`labels` can also be set as a [global default](#global-stack-wide-metadata).

Read one label with [`!labels key [default]`](/functions/yaml/labels#single-label-lookup),
or read the whole map with bare `!labels`:

```yaml
vars:
  compliance: !labels compliance
  owner: !labels owner "Platform Team"
  tags: !labels
```

Templates can read the same values with `{{ .metadata.labels.compliance }}` or
`{{ index .metadata.labels "cost-center" }}`. See
[runner selection](/cli/configuration/settings/pro#choose-a-runner-per-component-or-stack)
for using labels in Atmos Pro workflow inputs.

### Selector Purity

Labels and tags are selectors: Atmos evaluates them **before** authentication, template processing, and YAML function execution to decide which components a command operates on. By design, their values must be resolvable without authenticating or executing external processes — this keeps selection fast, deterministic, and credential-free.

Allowed in `metadata.tags` and `metadata.labels`:

- Plain strings
- Simple Go templates over the stack context (for example, `{{ .vars.stage }}`)
- Local YAML functions: `!env`, `!git.*`, `!include`

Rejected with a hard error on any command that enumerates stacks (`describe stacks`, `describe affected`, the `list` commands, and multi-component `terraform` selections such as `--all` and `--affected`):

- YAML functions that require authentication or process execution: `!terraform.state`, `!terraform.output`, `!store`, `!store.get`, `!secret`, `!aws.*`, `!emulator`, `!exec`, `!random`
- Template calls to `atmos.Component`, `atmos.Store`, `atmos.GomplateDatasource`, `atmos.Resolve`, gomplate datasources (`ds`/`datasource`, `include`, `datasourceExists`, `datasourceReachable`, `defineDatasource`), the gomplate `aws.*`/`gcp.*`/`net.*`/`random.*` namespaces, and network or non-deterministic template functions (`getHostByName`, `uuidv4`, `randAlpha`, `randAlphaNum`, `randNumeric`, `randAscii`, `randBytes`, `now`)

The error names the offending component and stack manifest. If a value needs one of these constructs, move it into `vars` or `settings` and reference it from there — the value stays available to the component while the selectors remain pure.

If upgrading surfaces this error for existing manifests and you need time to migrate, set `describe.settings.eager_evaluation: true` in `atmos.yaml`: it disables pre-evaluation scoping entirely (every stack is fully evaluated before filtering, matching pre-optimization behavior), under which selectors are evaluated like any other value and the purity contract is not enforced.

## Examples

### Multiple Instances from One Module

Deploy the same VPC module in different configurations:

**File:** `stacks/orgs/acme/plat/prod/us-east-1.yaml`

```yaml
components:
  terraform:
    vpc-main:
      metadata:
        component: vpc
        description: Main VPC
      vars:
        vpc_cidr: "10.0.0.0/16"
        name: main

    vpc-isolated:
      metadata:
        component: vpc
        description: Isolated VPC without an internet gateway
      vars:
        vpc_cidr: "10.1.0.0/16"
        name: isolated
        enable_internet_gateway: false
```

Both `vpc-main` and `vpc-isolated` use the same `components/terraform/vpc` root module but with different configurations.

### Abstract Base with Concrete Implementations

**File:** `stacks/catalog/vpc/_defaults.yaml`

```yaml
components:
  terraform:
    vpc/defaults:
      metadata:
        type: abstract
        component: vpc
      vars:
        enable_dns_hostnames: true
        enable_dns_support: true
        enable_nat_gateway: true
        single_nat_gateway: false
```

**File:** `stacks/orgs/acme/plat/prod/us-east-1.yaml`

```yaml
import:
  - catalog/vpc/_defaults

components:
  terraform:
    vpc:
      metadata:
        inherits:
          - vpc/defaults
      vars:
        vpc_cidr: "10.0.0.0/16"
        availability_zones:
          - us-east-1a
          - us-east-1b
          - us-east-1c
```

### Environment-Specific Component Control

**File:** `stacks/orgs/acme/plat/dev/us-east-1.yaml`

```yaml
components:
  terraform:
    expensive-feature:
      metadata:
        enabled: false  # Disabled in dev to save costs
      vars:
        # ...
```

**File:** `stacks/orgs/acme/plat/prod/us-east-1.yaml`

```yaml
components:
  terraform:
    expensive-feature:
      metadata:
        enabled: true # Enabled in production
      vars:
        # ...
```

### Multi-Level Inheritance

**File:** `stacks/catalog/components.yaml`

```yaml
components:
  terraform:
    # Level 1: Base defaults
    base/defaults:
      metadata:
        type: abstract
      vars:
        tags:
          ManagedBy: Atmos

    # Level 2: VPC defaults inheriting from base
    vpc/defaults:
      metadata:
        type: abstract
        component: vpc
        inherits:
          - base/defaults
      vars:
        enable_dns_hostnames: true

    # Level 3: Production VPC inheriting from vpc/defaults
    vpc/prod:
      metadata:
        type: abstract
        inherits:
          - vpc/defaults
      vars:
        enable_nat_gateway: true
        multi_az: true
```

## Best Practices

1. **Use Abstract Components:** Create abstract base components for shared configuration to keep your stacks DRY.

2. **Meaningful Component Names:** Use descriptive names that indicate the component's purpose and any specialization (e.g., `vpc/prod`, `vpc/isolated`).

3. **Document Inheritance:** Keep inheritance chains shallow (2-3 levels) and well-documented for maintainability.

4. **Protect Critical Components:** Use `locked: true` for infrastructure components that should not change without careful review.

5. **Use `enabled` for Environment Differences:** Rather than duplicating component definitions, use `enabled` to control which components are active per environment.

## Related

- [Inheritance](/howto/inheritance)
- [Catalogs](/howto/catalogs)
- [Overrides](/stacks/overrides)
- [Variables (vars)](/stacks/vars)
- [Terraform Components](/stacks/components/terraform)

```
```
