# Native CI

Atmos brings first-class CI/CD support directly into the CLI. Run Atmos commands in
GitHub Actions and get rich job summaries, status checks, output variables, and stored planfile
verification — no extra actions required.

> ⚠️ Experimental

## Quick Start

**File:** `atmos.yaml`

```yaml
ci:
  enabled: true
  summary:
    enabled: true
  output:
    enabled: true
    variables:
      - has_changes
      - has_additions
      - has_destructions
      - plan_summary
  checks:
    enabled: true
```

```shell
- name: Plan
  run: atmos terraform plan vpc -s prod
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy
  run: atmos terraform deploy vpc -s prod
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

**CI Configuration**

Configure CI providers, job summaries, output variables, status checks, planfile storage, and templates in your `atmos.yaml`.

Configuration Reference[Read more](/cli/configuration/ci)

## GitHub Actions Workflows

Running Atmos in GitHub Actions reduces to two steps: **check out the repository, then run an `atmos` command.** Atmos detects the CI environment automatically and produces job summaries, output variables, status checks, and stored planfiles without any wrapper actions.

The examples below pin the Atmos version via a [repository variable](https://docs.github.com/en/actions/learn-github-actions/variables) named `ATMOS_VERSION` (e.g. set to `1.200.0`). We don't publish a `latest` tag, so always pin to a specific release.

### Permissions

Atmos's native CI features rely on the standard GitHub Actions permission scopes — grant only what each workflow's triggers require:

| Feature | Permission |
| --- | --- |
| Job summaries (`$GITHUB_STEP_SUMMARY`) | none required |
| Output variables (`$GITHUB_OUTPUT`) | none required |
| Commit [status checks](/cli/configuration/ci/checks) | `statuses: write` |
| Check runs (modern Checks API) | `checks: write` |
| PR comments | `pull-requests: write` |
| Checkout | `contents: read` |
| OIDC token issuance | `id-token: write` |

There is no `comments: write` scope — PR comment writes use `pull-requests: write` (PR comments are issue comments under the hood). If you disable a feature in `atmos.yaml` (e.g. `ci.checks.enabled: false`), you can drop the matching permission.

### Plan on Pull Request

**File:** `.github/workflows/plan.yml`

```yaml
name: Plan
on:
  pull_request:

permissions:
  id-token: write
  contents: read
  statuses: write
  checks: write
  pull-requests: write

jobs:
  plan:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    steps:
      - uses: actions/checkout@v6

      - run: atmos terraform plan vpc -s prod
```

### Apply on Merge

**File:** `.github/workflows/apply.yml`

```yaml
name: Apply
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read
  statuses: write
  checks: write

jobs:
  apply:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    steps:
      - uses: actions/checkout@v6

      - run: atmos terraform deploy vpc -s prod
```

`atmos terraform deploy` runs a fresh plan and applies it with `-auto-approve`. Pass `--verify-plan` (or set `ATMOS_TERRAFORM_VERIFY_PLAN=true`) to additionally download the planfile uploaded during the PR run, generate a fresh plan, and perform a semantic comparison before applying — this opt-in verification is experimental. You can also run `atmos terraform apply` directly. See [Planfile Storage](/ci/planfile-storage) for details.

### Deploy Affected

Fan out across only the components that changed in the PR using `atmos describe affected --format=matrix`. When `ci.enabled: true` is set in `atmos.yaml`, the matrix is automatically written to `$GITHUB_OUTPUT` — no `--output-file` flag needed.

**File:** `.github/workflows/deploy-affected.yml`

```yaml
on:
  pull_request:

permissions:
  id-token: write
  contents: read
  statuses: write
  checks: write
  pull-requests: write

jobs:
  affected:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    outputs:
      matrix: ${{ steps.affected.outputs.matrix }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - id: affected
        run: atmos describe affected --format=matrix

  deploy:
    needs: affected
    if: ${{ needs.affected.outputs.matrix != '' }}
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    strategy:
      matrix: ${{ fromJson(needs.affected.outputs.matrix) }}
      fail-fast: false
    steps:
      - uses: actions/checkout@v6

      - env:
          COMPONENT: ${{ matrix.component }}
          STACK: ${{ matrix.stack }}
        run: atmos terraform deploy "$COMPONENT" -s "$STACK"
```

The `affected` job emits a matrix of `{component, stack}` pairs; the `deploy` job spreads across them in parallel.

### Deploy All

Fan out across **every** instance defined in your stacks using `atmos list instances --format=matrix`. Use this for full deploys, drift sweeps across the whole estate, or initial bootstraps. Like `describe affected`, it auto-routes to `$GITHUB_OUTPUT` when CI is enabled.

**File:** `.github/workflows/deploy-all.yml`

```yaml
on:
  workflow_dispatch:
  schedule:
    - cron: '0 6 * * *'   # Daily drift sweep

permissions:
  id-token: write
  contents: read
  statuses: write
  checks: write

jobs:
  inventory:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    outputs:
      matrix: ${{ steps.list.outputs.matrix }}
    steps:
      - uses: actions/checkout@v6

      - id: list
        run: atmos list instances --format=matrix

  deploy:
    needs: inventory
    if: ${{ needs.inventory.outputs.matrix != '' }}
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    strategy:
      matrix: ${{ fromJson(needs.inventory.outputs.matrix) }}
      fail-fast: false
    steps:
      - uses: actions/checkout@v6

      - env:
          COMPONENT: ${{ matrix.component }}
          STACK: ${{ matrix.stack }}
        run: atmos terraform deploy "$COMPONENT" -s "$STACK"
```

### Authentication

The shape of the story: **define a CI profile in `atmos.yaml`, point the workflow at it, done.** Atmos exchanges the GitHub OIDC token for cloud credentials transparently — there is no `atmos auth login` step in CI.

**1. Define a `github` [profile](/cli/configuration/profiles).** The profile name is arbitrary; we use `github` to match the [example repos](https://github.com/cloudposse-examples/atmos-native-ci). It holds the `github/oidc` provider plus the identity CI should use:

**File:** `atmos.yaml`

```yaml
auth:
  providers:
    github-oidc:
      kind: github/oidc
      region: us-east-1
  identities:
    plat-dev/terraform:
      provider: github-oidc
      role_arn: arn:aws:iam::111122223333:role/atmos-terraform
      default: true   # Use this identity unless a component overrides it
```

The IAM role's trust policy must allow GitHub's OIDC issuer for your repo — see [Configuring OpenID Connect in AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) for the trust-policy template. Other clouds work the same way: see [`azure/oidc`](/cli/configuration/auth/providers) and [`gcp/workload-identity-federation`](/cli/configuration/auth/providers).

**2. Pick how identities are selected.** All three work under the same profile:

- **One identity for everything**
  Mark one identity 
  `default: true`
   (as above), or set the 
  `ATMOS_IDENTITY`
   env var. Simplest setup — works when CI talks to one cloud account/role.
- **Per-component identity**
  Set 
  `settings.identity`
   on a component or stack to pick a different identity for that scope. Useful when prod components need a different role than dev.
- **Inheritance**
  Identities flow through the 
  [stack inheritance](/stacks)
   chain like everything else, so you can set the identity on a base stack and let descendants inherit (or override) it.

**3. Wire the workflow.** Two pieces: the `id-token: write` permission, and `ATMOS_PROFILE` set to the profile name.

**File:** `.github/workflows/apply.yml`

```yaml
permissions:
  id-token: write    # Required for GitHub to mint the OIDC token
  contents: read
  statuses: write
  checks: write

env:
  ATMOS_PROFILE: github

jobs:
  deploy:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    steps:
      - uses: actions/checkout@v6

      - run: atmos terraform deploy vpc -s prod
```

`id-token: write` lets GitHub issue the OIDC JWT; `ATMOS_PROFILE: github` activates the profile defined above. **No `atmos auth login` step is needed** — Atmos exchanges the OIDC token for cloud credentials when it runs the terraform command. (`atmos auth login` exists for interactive/local use; in CI it's redundant.)

### Gating Production with Environments

GitHub Actions [environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) buy you three things: per-environment secrets and variables, required manual approvals before the job runs, and deployment history visible in the GitHub UI. Wire one in by adding `environment:` to the deploy job:

**File:** `.github/workflows/apply.yml`

```yaml
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
    environment: prod    # Requires approval if configured in repo settings
    permissions:
      id-token: write
      contents: read
      statuses: write
      checks: write
    env:
      ATMOS_PROFILE: github
    steps:
      - uses: actions/checkout@v6

      - run: atmos terraform deploy vpc -s prod
```

The **GitHub environment** (`prod`) and the **Atmos stack** (`-s prod`) are independent concepts — one gates the workflow run, the other selects the stack configuration. Many teams happen to name them the same; nothing requires it.

For deeper auth reference: [Profiles](/cli/configuration/profiles), [Auth concepts](/stacks/auth), [Providers](/cli/configuration/auth/providers), [Identities](/cli/configuration/auth/identities).

**Working Examples**

Two reference repositories you can clone and adapt — both include `atmos.yaml`, stack configuration, components, and full workflows. They build on the patterns above with use-case-specific design patterns (preview environments, image promotion, label gating).

Basic Example[Read more](https://github.com/cloudposse-examples/atmos-native-ci)
 
Matrix Example[Read more](https://github.com/cloudposse-examples/atmos-native-ci-advanced)

## Features

### [Job Summaries](/ci/job-summaries)

Rich Markdown summaries with resource counts, inline badges, and collapsible diffs written
to `$GITHUB_STEP_SUMMARY`. Includes separate templates for plan and apply results. Templates
are fully customizable with Go template syntax.

### [Outputs](/cli/configuration/ci/output)

Terraform plan and apply results exported as CI output variables for use in downstream jobs.
On GitHub Actions, these are written to `$GITHUB_OUTPUT`.

### [Checks](/cli/configuration/ci/checks)

Live commit status checks showing real-time operation progress — "Plan in progress" while
running and "3 to add, 1 to change, 0 to destroy" when complete.

### [Planfile Storage](/ci/planfile-storage)

Store and retrieve planfiles across CI pipeline stages using S3, GitHub Artifacts, or local
filesystem. The `deploy` command downloads stored planfiles, generates a fresh plan, and
performs a semantic comparison to detect drift before applying.

## Commands

CI features are activated with the `--ci` flag on existing Terraform commands or automatically when running in a CI environment (e.g. GitHub Actions):

- **[`atmos terraform plan [--ci]`](/cli/commands/terraform/plan)**
  Run plan with job summary, output variables, status checks, and planfile upload.
- **[`atmos terraform apply [--ci]`](/cli/commands/terraform/apply)**
  Run apply with job summary, output variables, and status checks.
- **[`atmos terraform deploy [--ci]`](/cli/commands/terraform/deploy)**
  Deploy with stored planfile verification, drift detection, and full CI reporting.
- **[`atmos terraform planfile`](/cli/commands/terraform/planfile)**
  Manage stored planfiles: upload, download, list, delete, and show.
- **[`atmos describe affected --format=matrix`](/cli/commands/describe/affected)**
  Generate GitHub Actions matrix strategy from affected components.

## Providers

Atmos auto-detects the CI environment and selects the appropriate provider:

- ****GitHub Actions****
  Integrates with GitHub job summaries, commit status checks, and output variables. Requires 
  `GITHUB_TOKEN`
   for checks and PR features.
- ****Generic CI****
  Prints summaries, checks, and outputs to stdout. Useful for local development and testing, or any CI provider without native integration.

:::note Looking for our old GitHub Actions?
The Cloud Posse `cloudposse/github-action-atmos-terraform-*` actions have been [deprecated](/deprecated/github-actions) in favor of native CI. Existing workflows still function, but new projects should use the patterns above.
:::

## Related

- [CI Configuration](/cli/configuration/ci) - Configure CI integration in `atmos.yaml`
- [CI Commands](/cli/commands/ci) - CI command reference
- [Profiles](/cli/configuration/profiles) - Configure CI-specific profiles
- [Auth](/stacks/auth) - Configure OIDC authentication for CI
