# !terraform.state

The `!terraform.state` YAML function is the **fastest** way to read Terraform/OpenTofu outputs ([remote state](/stacks/remote-state))
in Atmos stack manifests. It retrieves outputs directly from the configured [backends](/components/terraform/backends)
without the overhead of initializing Terraform, downloading providers, or generating configuration files - making it significantly faster than `!terraform.output`.

:::note Supported Backend Types
The `!terraform.state` YAML function supports the following backend types:

- `local` ([Terraform](https://developer.hashicorp.com/terraform/language/settings/backends/local) and [OpenTofu](https://opentofu.org/docs/language/settings/backends/local))
- `s3` ([Terraform](https://developer.hashicorp.com/terraform/language/settings/backends/s3) and [OpenTofu](https://opentofu.org/docs/language/settings/backends/s3))
- `gcs` ([Terraform](https://developer.hashicorp.com/terraform/language/settings/backends/gcs) and [OpenTofu](https://opentofu.org/docs/language/settings/backends/gcs))
- `azurerm` ([Terraform](https://developer.hashicorp.com/terraform/language/settings/backends/azurerm) and [OpenTofu](https://opentofu.org/docs/language/settings/backends/azurerm))

As support for new backend types is added, this document will be updated accordingly.

For other backends, use [**`!store`**](/functions/yaml/store) or [**`!terraform.output`**](/functions/yaml/terraform.output) to read remote state
and [share data between components](/stacks/share-data).
:::

## Usage

The `!terraform.state` function can be called with either two or three parameters:

```yaml
  # Get the `output` of the `component` in the current stack
  !terraform.state <component> <output>

  # Get the `output` of the `component` in the provided `stack`
  !terraform.state <component> <stack> <output>

  # Get the output of the `component` by evaluating the YQ expression
  !terraform.state <component> <yq-expression>

 # Get the output of the `component` in the provided `stack` by evaluating the YQ expression
  !terraform.state <component> <stack> <yq-expression>
```

## Arguments

- **`component`**
  Atmos component name
- **`stack`**
  (Optional) Atmos stack name
- **`output` or `yq-expression`**
  Terraform output or 
  [YQ](https://mikefarah.gitbook.io/yq)
   expression to evaluate the output

:::tip
You can use [Atmos Stack Manifest Templating](/templates) in the `!terraform.state` YAML function expressions.
Atmos processes the templates first, and then executes the `!terraform.state` function, allowing you to provide the parameters to
the function dynamically.
:::

:::note Type-Aware Merging
Atmos supports type-aware merging of YAML functions and concrete values, allowing them to coexist in the inheritance chain without type conflicts.
See the full explanation: [YAML Function Merging](/reference/yaml-function-merging)
:::

## `!terraform.state` Function Execution Flow

When processing the `!terraform.state` YAML function for a component in a stack, Atmos executes the following steps:

- **Stack and Component Context Resolution**
  Atmos resolves the full context for the specified component within the given stack, including all inherited and merged
  configuration layers (globals, environment and component-level config).

- **Terraform State Backend Lookup and Read**
  Based on the resolved context, Atmos identifies the corresponding backend for the component in the stack
  and reads the state file directly from the backend. If access to the backend requires a role assumption
  (e.g. `assume_role.role_arn` for the `s3` backend), Atmos assumes the role before accessing the backend state file.

- **Output Parsing and Interpolation**
  The relevant output variable is extracted from the state file (using a [YQ](https://mikefarah.gitbook.io/yq/) parser).
  Atmos parses and interpolates the value into the final configuration structure, replacing the `!terraform.state`
  directive in the YAML stack manifest with the final value.

## Performance Comparison

`!terraform.state` is **dramatically faster than `!terraform.output`** - often 10-100x faster depending on your infrastructure size.

While `!terraform.output` must initialize Terraform/OpenTofu, download and initialize all providers, generate varfiles and backend configs, and execute terraform commands, `!terraform.state` bypasses all of this overhead by reading directly from the state backend.

:::tip
Use `!terraform.state` when you need the **fastest possible** access to Terraform outputs. The functions accept the same parameters and produce identical results.
:::

Compare the [!terraform.output Execution Flow](/functions/yaml/terraform.output#terraformoutput-function-execution-flow) with the [!terraform.state Execution Flow](/functions/yaml/terraform.state#terraformstate-function-execution-flow) to see the difference.

## Using YQ Expressions to retrieve items from complex output types

To retrieve items from complex output types such as maps and lists, or do any kind of filtering or querying,
you can utilize [YQ](https://mikefarah.gitbook.io/yq) expressions.

For example:

- Retrieve the first item from a list

```yaml
subnet_id1: !terraform.state vpc .private_subnet_ids[0]
```

- Read a key from a map

```yaml
username: !terraform.state config .config_map.username
```

For more details, review the following docs:

- [YQ Guide](https://mikefarah.gitbook.io/yq)
- [YQ Recipes](https://mikefarah.gitbook.io/yq/recipes)

## Handling YQ Expressions with Bracket Notation and Quotes

When you access map keys that contain special characters (such as hyphens) by using YQ bracket notation, you must wrap the key in double quotes like `["github-dependabot"]`. This often clashes with the surrounding quotes that protect the entire expression in YAML.

To avoid conflicting quotes, wrap the entire YQ expression in single quotes while keeping the double quotes inside the brackets:

```yaml
# Use single quotes around the expression to allow double quotes inside brackets
access_key_id: !terraform.state security '.users["github-dependabot"].access_key_id'
```

Additional examples:

```yaml
# Access map keys with special characters
api_key: !terraform.state config '.api_keys["service-account-1"]'

# Access nested maps with special characters in multiple levels
endpoint: !terraform.state services '.endpoints["my-service"]["production"]'

# Combine with stack templating
token: !terraform.state identity {{ .stack }} '.tokens["github-actions"]'

# Escape single quotes by doubling them when needed inside the expression
app_name: !terraform.state config '.apps["app''s-name"].display_name'
```

**Quote escaping rules**

- Wrap the entire YQ expression in single quotes when it contains double quotes.
- Use double quotes inside brackets for string keys in YQ expressions.
- If you need single quotes inside the expression, escape them by doubling: `''`.

:::tip
This quoting pattern works for every Atmos YAML function that accepts YQ expressions, including [!terraform.output](/functions/yaml/terraform.output) and [!include](/functions/yaml/include).
:::

## Using YQ Expressions to provide a default value

If the component for which you are reading the state has not been provisioned yet, or if the specific output doesn't exist,
you can specify a [default value](https://mikefarah.gitbook.io/yq/operators/alternative-default-value)
in the YQ expression using the `//` operator. Atmos will evaluate the default when the data is unavailable.

This allows you to mock outputs when executing `atmos terraform plan` where there are dependencies between components,
and the dependent components are not provisioned yet.

:::tip Looking for reusable, component-owned mock fixtures?
A `//` default is a one-off fallback baked into a single expression. If you want a producer
component to declare its mock outputs once and have every consumer resolve them consistently,
use the dedicated [`mocks`](/stacks/components/mocks) stack-config section with the
`--use-mocks` flag instead — see [Component Mocks for Terraform YAML Lookups](/changelog/terraform-component-mocks).
A `//` default in the caller's expression still applies even when the referenced component
declares no `mocks` section at all; without one, an undeclared `mocks` section is a hard error.
:::

:::tip Default Value Behavior
Atmos distinguishes between **recoverable errors** (component not provisioned, output missing) and **non-recoverable errors** (backend API failures):

- **Recoverable errors with defaults**: When a component's state doesn't exist or an output is missing, AND you specify a YQ default (`//`), Atmos uses the default value
- **Recoverable errors without defaults**: Returns an error when state doesn't exist, or `null` for missing outputs
- **Non-recoverable errors**: Backend API failures (S3 access denied, GCS timeouts, Azure connectivity issues) always propagate as errors, even if a default is specified

This ensures that infrastructure failures are never silently masked by default values.
:::

:::note
`!terraform.state` parses the component, optional stack, and the remaining YQ expression directly. No parser-specific quote escaping is required.

YAML is parsed first: compact JSON such as `{"key":"value"}` can be a plain scalar; use a single-quoted YAML scalar when readable JSON contains `: `; and attach the tag to a folded scalar for multi-line expressions.

```yaml
test_map: !terraform.state >-
  component-2 .output // {"key1": "fallback1"}
```

:::

For example:

- Specify a string default value.
  Read the `username` output from the `config` component in the current stack.
  If the `config` component has not been provisioned yet, return the default value `default-user`.

```yaml
username: !terraform.state config .username // "default-user"
```

- Specify a list default value.
  Read the `private_subnet_ids` output from the `vpc` component in the current stack.
  If the `vpc` component has not been provisioned yet, return the default value `["mock-subnet1", "mock-subnet2"]`.

```yaml
subnet_ids: !terraform.state vpc .private_subnet_ids // ["mock-subnet1", "mock-subnet2"]
```

- Specify a map default value.
  Read the `config_map` output from the `config` component in the current stack.
  If the `config` component has not been provisioned yet, return the default value `{"api_endpoint": "localhost:3000", "user": "test"}`.

```yaml
config_map: !terraform.state 'config .config_map // {"api_endpoint": "localhost:3000", "user": "test"}'
```

For more details, review the following docs:

- [YQ Alternative (Default value)](https://mikefarah.gitbook.io/yq/operators/alternative-default-value)

## Using YQ Expressions to modify values returned from the remote state

Since the `output` parameter of the `!terraform.state` function is a [YQ](https://mikefarah.gitbook.io/yq) expression,
you can use [YQ pipes](https://mikefarah.gitbook.io/yq/operators/pipe) and
[YQ operators](https://mikefarah.gitbook.io/yq/operators) (including [YQ string concatenation functions](https://mikefarah.gitbook.io/yq/operators/add#string-concatenation)
and [YQ arithmetic functions](https://mikefarah.gitbook.io/yq/operators/add))
to modify the values returned from the remote state.

For example, suppose you have an `aurora-postgres` Atmos component which has the output `master_hostname`.

To read the output without modification, you can use the following expressions:

```yaml
postgres_url: !terraform.state aurora-postgres master_hostname
```

```yaml
postgres_url: !terraform.state aurora-postgres .master_hostname
```

To prepend and append strings to the output, you can use YQ pipes and the `add` function (`+` operator):

```yaml
postgres_url: !terraform.state 'aurora-postgres .master_hostname | "jdbc:postgresql://" + . + ":5432/events"'
```

The outer single quotes in the example are YAML quoting, not function-parser escaping. They preserve the readable YQ string literals unchanged.

After the `!terraform.state` function is executed, the `postgres_url` variable will have the final value similar to:

```yaml
postgres_url: "jdbc:postgresql://aurora-postgres-cluster-writer.prod.plat.mydomain.net:5432/events"
```

For more details, review the following docs:

- [YQ pipe](https://mikefarah.gitbook.io/yq/operators/pipe)
- [YQ operators](https://mikefarah.gitbook.io/yq/operators)
- [YQ string concatenation](https://mikefarah.gitbook.io/yq/operators/add#string-concatenation)
- [YQ `add` function](https://mikefarah.gitbook.io/yq/operators/add)

## Examples

**File:** `stack.yaml`

```yaml
components:
  terraform:
    my_lambda_component:
      vars:
        vpc_config:
          # Output of type string
          security_group_id: !terraform.state security-group/lambda id
          security_group_id2: !terraform.state security-group/lambda2 {{ .stack }} id
          security_group_id3: !terraform.state security-group/lambda3 {{ .atmos_stack }} id
          # Output of type list
          subnet_ids: !terraform.state vpc private_subnet_ids
          # Use a YQ expression to get an item from the list
          subnet_id1: !terraform.state vpc .private_subnet_ids[0]
          # Output of type map
          config_map: !terraform.state config {{ .stack }} config_map
          # Use a YQ expression to get a value from the map
          username: !terraform.state config .config_map.username
```

## Specifying Atmos `stack`

If you call the `!terraform.state` function with three parameters, you need to specify the stack as the second argument.

There are multiple ways you can specify the Atmos stack parameter in the `!terraform.state` function.

### Hardcoded Stack Name

Use it if you want to get an output from a component from a different (well-known and static) stack.
For example, you have a `tgw` component in a stack `plat-ue2-dev` that requires the `vpc_id` output from the `vpc` component from the stack `plat-ue2-prod`:

```yaml title="plat-ue2-dev"
  components:
    terraform:
      tgw:
        vars:
          vpc_id: !terraform.state vpc plat-ue2-prod vpc_id
```

### Reference the Current Stack Name

Use the `.stack` (or `.atmos_stack`) template identifier to specify the same stack as the current component is in
(for which the `!terraform.state` function is executed):

```yaml
  !terraform.state <component> {{ .stack }} <output>
  !terraform.state <component> {{ .atmos_stack }} <output>
```

For example, you have a `tgw` component that requires the `vpc_id` output from the `vpc` component in the same stack:

```yaml
  components:
    terraform:
      tgw:
        vars:
          vpc_id: !terraform.state vpc {{ .stack }} vpc_id
```

:::note
Using the `.stack` or `.atmos_stack` template identifiers to specify the stack is the same as calling the `!terraform.state`
function with two parameters without specifying the current stack, but without using `Go` templates.
If you need to get an output of a component in the current stack, using the `!terraform.state` function with two parameters
is preferred because it has a simpler syntax and executes faster.
:::

### Use a Format Function

Use the `printf` template function to construct stack names using static strings and dynamic identifiers.
This is convenient when you want to override some identifiers in the stack name:

```yaml
  !terraform.state <component> {{ printf "%s-%s-%s" .vars.tenant .vars.environment .vars.stage }} <output>

  !terraform.state <component> {{ printf "plat-%s-prod" .vars.environment }} <output>

  !terraform.state <component> {{ printf "%s-%s-%s" .settings.context.tenant .settings.context.region .settings.context.account }} <output>
```

- **`<component>`**
  Placeholder for an actual component name (e.g. 
  `vpc`
  )
- **`<output>`**
  Placeholder for an actual Terraform output (e.g. 
  `subnet_ids`
  )

For example, you have a `tgw` component deployed in the stack `plat-ue2-dev`. The `tgw` component requires the
`vpc_id` output from the `vpc` component from the same environment (`ue2`) and same stage (`dev`), but from a different
tenant `net` (instead of `plat`):

```yaml title="plat-ue2-dev"
  components:
    terraform:
      tgw:
        vars:
          vpc_id: !terraform.state vpc {{ printf "net-%s-%s" .vars.environment .vars.stage }} vpc_id
```

:::tip Important
By using the `printf "%s-%s-%s"` function, you are constructing stack names using the stack context variables/identifiers.

For more information on Atmos stack names and how to define them, refer to `stacks.name_pattern` and `stacks.name_template`
sections in [`atmos.yaml` CLI config file](/cli/configuration/)
:::

## Caching the result of `!terraform.state` function

Atmos caches (in memory) the results of `!terraform.state` function.

The cache is per Atmos CLI command execution, e.g., each new execution of a command like `atmos terraform plan`,
`atmos terraform apply` or `atmos describe component` will create and use a new memory cache, which involves re-reading the remote state after reinitialisation.

If you define the function in stack manifests for the same component in a stack more than once, the first call will
produce the result and cache it, and all the consecutive calls will just use the cached data. This is useful when you use the
`!terraform.state` function for the same component in a stack in multiple places in Atmos stack manifests.
It will speed up the function execution and stack processing.

For example:

```
components:
  terraform:
    test2:
      vars:
        tags:
          test: !terraform.state test id
          test2: !terraform.state test id
          test3: !terraform.state test {{ .stack }} id
```

In the example, the `test2` Atmos component uses the outputs (remote state) of the `test` Atmos component from the same stack.
The YAML function `!terraform.state` is executed three times (once for each tag).

After the first execution, Atmos caches the result in memory,
and reuses it in the next two calls to the function. The caching makes the stack processing much faster.
In a production environment where many components are used, the speedup can be significant.

## Using `!terraform.state` with S3 SSE-C Encrypted State

If your S3 backend uses [SSE-C (Server-Side Encryption with Customer-Provided Keys)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html),
you need to provide the customer encryption key so that Atmos can decrypt the state file when reading it directly from S3.

This follows the same configuration conventions as [OpenTofu](https://opentofu.org/docs/language/settings/backends/s3/#sse_customer_key)
and [Terraform](https://developer.hashicorp.com/terraform/language/backend/s3#sse_customer_key).

### Configuration

Provide the SSE-C key using either the `sse_customer_key` backend attribute or the `AWS_SSE_CUSTOMER_KEY` environment variable.
The key must be a base64-encoded 256-bit (32-byte) encryption key.

**File:** `stacks/orgs/acme/_defaults.yaml`

```yaml
terraform:
  backend_type: s3
  backend:
    s3:
      bucket: acme-ue1-root-tfstate
      region: us-east-1
      key: "terraform.tfstate"
      encrypt: true
      use_lockfile: true
      sse_customer_key: "base64-encoded-32-byte-key"
```

Alternatively, set the key via an environment variable to avoid storing it in stack configuration:

```shell
export AWS_SSE_CUSTOMER_KEY="base64-encoded-32-byte-key"
```

:::tip
The backend attribute `sse_customer_key` takes precedence over the `AWS_SSE_CUSTOMER_KEY` environment variable.
:::

### Remote State Backend Override

When using a separate `remote_state_backend` configuration for reading state (e.g., with a read-only role),
the `sse_customer_key` can also be specified in the `remote_state_backend` section:

```yaml
terraform:
  remote_state_backend_type: s3
  remote_state_backend:
    s3:
      role_arn: "arn:aws:iam::xxxxxxxx:role/terraform-backend-read-only"
      sse_customer_key: "base64-encoded-32-byte-key"
```

:::note
SSE-C support only applies to the `!terraform.state` function, which reads state files directly from S3.
The `!terraform.output` function is unaffected because it delegates to `terraform output`, which handles encryption internally.
:::

## Using `!terraform.state` with GCS backend

The GCS backend support allows you to read Terraform state files stored in Google Cloud Storage buckets. Here's an example configuration:

**File:** `stacks/gcp/dev.yaml`

```yaml
components:
  terraform:
    my_component:
      backend_type: gcs
      backend:
        gcs:
          bucket: "my-terraform-state"
          prefix: "terraform/state"
          credentials: "/path/to/service-account-key.json"
      vars:
        # Read state from another component
        vpc_id: !terraform.state vpc dev vpc_id
        subnet_ids: !terraform.state subnets dev subnet_ids
        # Use YQ expression to get specific subnet
        public_subnet_id: !terraform.state subnets dev .subnet_ids[0]
```

### GCS Backend Authentication

The GCS backend supports multiple authentication methods:

1. **Service Account Key File**: Specify the path to a service account JSON key file using the `credentials` parameter.
2. **Default Credentials**: When no credentials are specified, the Google Cloud SDK default credentials are used.
3. **Workload Identity** (GKE): Automatically uses the workload identity when running in GKE with Workload Identity enabled.

### GCS Backend Configuration Parameters

- `bucket`: The GCS bucket name where Terraform state files are stored
- `prefix`: Optional prefix for the state file path within the bucket
- `credentials`: Optional path to a service account JSON key file
- `state_file`: Optional custom name for the state file (defaults to `default.tfstate`)

:::note Current Limitations
The `impersonate_service_account` parameter is parsed but not yet implemented. This feature is planned for a future release.
:::

## Using `!terraform.state` with `static` remote state backend

Atmos supports [brownfield configuration by using the remote state of type `static`](/components/terraform/brownfield/#hacking-remote-state-with-static-backends).

For example:

**File:** `stack.yaml`

```yaml
components:
  terraform:
    # Component `static-backend` is configured with the remote state backend of type `static`
    static-backend:
      remote_state_backend_type: static
      remote_state_backend:
        static:
          region: "us-west-2"
          cluster_name: "production-cluster"
          vpc_cidr: "10.0.0.0/16"
          database:
            type: "postgresql"
            version: "12.7"
            storage_gb: 100
          allowed_ips:
            - "192.168.1.0/24"
            - "10.1.0.0/16"
          tags:
            Environment: "production"
            Owner: "infra-team"

    eks-cluster:
      vars:
        region: !terraform.state static-backend region
        cluster_name: !terraform.state static-backend cluster_name
        vpc_cidr: !terraform.state static-backend vpc_cidr
        db_type: !terraform.state static-backend database.type
        db_storage: !terraform.state static-backend database.storage_gb
        allowed_ips: !terraform.state static-backend allowed_ips
        tags: !terraform.state static-backend tags
```

When the functions are executed, Atmos detects that the `static-backend` component has the `static` remote state configured,
and instead of executing `terraform output`, it just returns the static values from the `remote_state_backend.static` section.

Executing the command `atmos describe component eks-cluster -s <stack>` produces the following result:

```shell
vars:
  region: us-west-2
  cluster_name: production-cluster
  vpc_cidr: 10.0.0.0/16
  db_type: postgresql
  db_storage: 100
  allowed_ips:
    - 192.168.1.0/24
    - 10.1.0.0/16
  tags:
    Environment: production
    Owner: infra-team
```

## Switching AWS credentials per component via the `env` section

:::note Advanced — most users do not need this
This section covers an opt-in behavior used by setups that distribute Terraform state across multiple AWS accounts or organizations and discriminate them by a per-stack `env.AWS_PROFILE` (or similar). If you keep all state in a single account, you can skip this section — the default credential chain handles you correctly.
:::

When the calling stack and the target stack of `!terraform.state` live in **different AWS accounts**, `!terraform.state` needs to authenticate against the target account's backend. For users on [Atmos auth](/stacks/auth), the configured identity is the canonical source. For users **not yet on Atmos auth**, Atmos also honors a whitelisted subset of the target component's `env` section so that `!terraform.state` matches the credential resolution `!terraform.output` already performs via its subprocess.

This makes the two functions behave symmetrically: anything that works for `!terraform.output` because of the component's `env` (the loop in `pkg/terraform/output/environment.go::SetupEnvironment`) now also works for `!terraform.state`.

### Whitelisted env keys (AWS)

Only credential- and endpoint-related env vars are read from the target component's `env` section — arbitrary env vars are **not** exposed to the in-process backend client:

```
AWS_PROFILE              AWS_CONFIG_FILE
AWS_REGION               AWS_SHARED_CREDENTIALS_FILE
AWS_DEFAULT_REGION       AWS_ENDPOINT_URL_S3
AWS_USE_FIPS_ENDPOINT    AWS_ENDPOINT_URL_STS
```

A component with no whitelisted env key produces no overlay and resolves credentials exactly as before. This is a strict no-op for setups that don't use this pattern.

`AWS_STS_REGIONAL_ENDPOINTS` is intentionally not honored — it was a SDK v1 toggle, and SDK v2 always uses regional endpoints by default. Setting it has no effect on the in-process client.

### Precedence

When `!terraform.state` constructs its in-process AWS client, credential sources resolve in this order (lowest wins → highest wins):

1. Process environment (whatever `AWS_PROFILE` etc. is set in the shell running Atmos).
2. The target component's S3 backend `profile` attribute (see below).
3. The target component's whitelisted `env` overlay (this section).
4. Atmos auth (`AWSAuthContext`) — wins outright when configured.

### Backend `profile` attribute

If the target component's `backend.s3` section sets `profile`, `!terraform.state` uses it as `AWS_PROFILE` whenever
neither Atmos auth nor the component's `env.AWS_PROFILE` selects credentials. This is the same precedence
`terraform init` applies to the `profile` attribute it finds in `backend.tf.json`, so a stack that only sets the
profile on the backend (a common pattern when every stage maps to its own named profile) resolves the same
credentials for `!terraform.state` as it already does for `!terraform.output`:

```yaml
terraform:
  backend_type: s3
  backend:
    s3:
      bucket: !template '{{ .vars.stage }}-tfstate'
      region: us-west-2
      profile: !template '{{ .vars.stage }}:terraform'
      assume_role:
        role_arn: !template 'arn:aws:iam::{{ .vars.account_id }}:role/terraform'
```

With this configuration, `!terraform.state vpc {{ .stack }} vpc_id` loads the `<stage>:terraform` profile and then
assumes `assume_role.role_arn`, exactly as the backend does during `terraform init`. No `env` section is required.

### Example: per-namespace AWS profile

A common SweetOps pattern: each org has its own AWS account, each stack tree declares the matching profile in `env`, and the backend block targets that org's tfstate bucket.

**File:** `stacks/orgs/dev/_defaults.yaml`

```yaml
env:
  AWS_PROFILE: "dev-identity"
vars:
  namespace: "dev"
terraform:
  backend:
    s3:
      bucket: dev-tfstate
      assume_role:
        role_arn: arn:aws:iam::111111111111:role/dev-tfstate
      region: us-east-1
      key: terraform.tfstate
```

**File:** `stacks/orgs/prod/_defaults.yaml`

```yaml
env:
  AWS_PROFILE: "prod-identity"
vars:
  namespace: "prod"
terraform:
  backend:
    s3:
      bucket: prod-tfstate
      assume_role:
        role_arn: arn:aws:iam::222222222222:role/prod-tfstate
      region: us-east-1
      key: terraform.tfstate
```

A component in `dev-...` reading a component in `prod-...`:

```yaml
peered_vpc_id: !terraform.state vpc prod-tenant-region-stage vpc_id
```

When Atmos resolves this, it picks up `componentSections.env.AWS_PROFILE = "prod-identity"` from the target component's stack config, uses that profile as the source for `sts:AssumeRole` into the prod-tfstate role, and reads the prod bucket. Without the overlay, the call would use the dev shell's `AWS_PROFILE` and fail with `AccessDenied`.

### When to use Atmos auth instead

If you're rolling out [Atmos auth](/stacks/auth) across components, prefer it. The `env` overlay path is layered **below** Atmos auth and is intended for setups that haven't yet migrated. Setting both is safe: Atmos auth's `AWSAuthContext` wins and the overlay is ignored for that call.

### Coverage

The env overlay applies to the **S3** backend reader. The `gcs` and `azurerm` backend readers do not yet honor `componentSections.env` — track follow-up work in [cloudposse/atmos](https://github.com/cloudposse/atmos/issues) if you need this behavior for those backends.

## Considerations

- Using `!terraform.state` with secrets can expose sensitive data to standard output (stdout) in any commands that describe stacks or components.

- When using `!terraform.state` with [`atmos describe affected`](/cli/commands/describe/affected), Atmos requires access to all referenced remote states.
  If you operate with limited permissions (e.g., scoped to `dev`) and reference production stacks, the command will fail.

- Overusing the function within stacks to reference multiple components can impact performance.

- Be mindful of disaster recovery (DR) implications when using it across regions.

- Consider cold-start scenarios: if the referenced component's state file doesn't exist (e.g., not yet provisioned), `!terraform.state` returns an error unless you provide a YQ default value using the `//` operator. See [Using YQ Expressions to provide a default value](#using-yq-expressions-to-provide-a-default-value) for details on error handling behavior.
