# 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. And because every command is **git-aware**, the same
CLI powers GitOps end to end: plan and apply only what changed, vendor reusable catalogs across
repositories, and commit results back to your source of truth. Kubernetes components also write
native job summaries, with a deliberately smaller v1 surface.

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

Terraform commands use the full native CI feature set. Kubernetes commands currently emit
human-readable job summaries only; they do not write `$GITHUB_OUTPUT` values, commit statuses,
PR comments, or stored artifacts.

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.

### Validate Workflows

Lint GitHub Actions workflows directly with Atmos before running infrastructure commands:

```yaml title=".github/workflows/validate.yml"
- name: Validate workflows
  run: atmos validate ci
```

`atmos validate ci` is an alias for [`atmos ci validate`](/cli/commands/ci/validate). With no file arguments it checks every `.yml` and `.yaml` workflow under the current working directory's `.github/workflows`. In GitHub Actions it emits inline annotations when `ci.enabled: true` and annotations are enabled. Use `--format=sarif` to write a SARIF document for an explicit code-scanning upload step.

To lint workflow fixtures or another workflow directory, pass `--workflow-path`. The repository includes an intentionally invalid scenario that is useful for a local smoke test:

```shell
atmos ci validate --workflow-path tests/fixtures/scenarios/invalid-github-actions-workflows/.github/workflows
```

The command reports the invalid `branch` key and exits with status 1.

Atmos respects `.github/actionlint.yaml` or `.github/actionlint.yml`, including custom self-hosted runner labels. See [`atmos ci validate`](/cli/commands/ci/validate) for file selection, output formats, and configuration details.

### 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`. When [planfile storage](/ci/planfile-storage) is configured under `components.terraform.planfiles` in `atmos.yaml`, a CI deploy **automatically** downloads the planfile uploaded during the PR run, generates a fresh plan, and performs a semantic comparison before applying (failing on drift by default). The `--verify-plan` flag (or `ATMOS_TERRAFORM_VERIFY_PLAN=true`) is only a per-run override — it forces verification on (and `--verify-plan=false` forces it off), and it **requires planfile storage to be configured**: without it there is no stored plan to verify against, and the deploy errors. You can also run `atmos terraform apply` directly. See [Planfile Storage](/ci/planfile-storage) for details.

:::note
Storing planfiles in the `github/artifacts` backend requires the runner's artifact credentials,
which GitHub withholds from `run:` steps. Add the
[`github-runtime` action](/ci/planfile-storage#using-github-artifacts-in-github-actions) before the
`plan`/`deploy` steps in the workflows above.
:::

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

### Caching the Toolchain

Every CI job reinstalls the same toolchain (Terraform, Helm, and friends). The **build cache** restores the Atmos cache root — which includes the toolchain install path — at the start of a job and saves it at the end, using the same GitHub Actions cache store that `actions/cache` uses. A warm cache turns a multi-minute toolchain install into a near-instant restore.

The most secure, lowest-boilerplate wiring is the `actions/cache` composite: Atmos derives the cache key and paths, native `actions/cache` does the storage, and **no runtime token is exposed to your job.**

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

```yaml
steps:
      - uses: actions/checkout@v6

      - uses: cloudposse/atmos/actions/cache@v1   # pin to a release or SHA

      - run: atmos toolchain install              # near-instant on a cache hit
      - run: atmos terraform plan vpc -s prod
```

Prefer fully automatic caching? Set `ci.cache.auto: both` in `atmos.yaml` and Atmos restores on start and saves on exit for every invocation — no extra workflow steps:

**File:** `atmos.yaml`

```yaml
ci:
  cache:
    enabled: true     # master switch (required)
    auto: both        # restore on start AND save on end
```

You can also drive the cache explicitly with the [`atmos ci cache`](/cli/commands/ci/cache) subcommands (`restore`, `save`, `paths`, `list`, `delete`). The cache key defaults to a hash of the toolchain lockfile plus OS/arch with a prefix restore-key fallback, mirroring `actions/cache`; entries are write-once, so an exact hit skips the save. See the [cache configuration reference](/cli/configuration/ci/cache) for keys, paths, and the four GitHub Actions integration options with their security trade-offs.

### 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, collapsible diffs, and captured
command output written to `$GITHUB_STEP_SUMMARY`. Terraform includes plan/apply summaries plus
the broader native CI features below. Helm and Helmfile currently write summaries only.
Templates are fully customizable with Go template syntax.

Kubernetes summaries are intentionally compact: `plan`/`diff` show created, changed, and
no-change objects; `apply`/`deploy` show applied or delivered objects; `delete` shows deleted
and not-found objects; `validate` shows valid and invalid objects plus errors.

### [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`. Kubernetes commands do not emit
output variables in v1.

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

### [Build Cache](/cli/configuration/ci/cache)

Warm-start the toolchain across CI jobs by restoring and saving the Atmos cache root via the
CI provider's cache store — the same store `actions/cache` uses. Runs automatically with
`ci.cache.auto: both`, or explicitly with the [`atmos ci cache`](/cli/commands/ci/cache)
subcommands.

### [GitOps](/cli/configuration/git)

Atmos is **git-aware**, which is what makes true GitOps possible: the repository is the source of
truth, and the pipeline reconciles only what changed.

- **Plan what's affected**
  [`atmos describe affected`](/cli/commands/describe/affected)
   diffs two Git commits and reports exactly which components and stacks changed — including changes that ripple through dependencies, imports, and remote state. See 
  [Deploy Affected](#deploy-affected)
   for the matrix workflow.
- **Apply only what changed**
  Fan out across just the affected components, so a PR plans — and a merge applies — only the work that actually changed, instead of re-running the entire estate on every commit.
- **Reusable across repositories**
  Publish service catalogs and module libraries once and 
  [vendor](/vendor/)
   them into every workload repository, so many repos share one versioned source of truth instead of copy-pasting configuration.
- **Automate workload repositories**
  Commit generated artifacts back to a source-of-truth repository as part of the pipeline. Define managed repositories once under 
  `git.repositories`
  , then have Atmos commit and push automatically — with signed commits, a bot author identity, and bounded non-fast-forward retries — from 
  [`kind: git` hooks](/stacks/hooks#kind-git)
  , the 
  [`atmos git`](/cli/commands/git/usage)
   commands, or CI workflows. This is the foundation for GitOps with Argo CD, Flux, or downstream CI consuming the committed output.

## Commands

CI features are activated with the `--ci` flag on supported 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 helm template|diff|apply|deploy|delete [--ci]`](/cli/commands/helm/usage)**
  Run native Helm components with job summaries for rendered/applied object metadata.
- **[`atmos helmfile template|diff|apply|sync|deploy|destroy [--ci]`](/cli/commands/helmfile/usage)**
  Run Helmfile components with job summaries that include captured masked command output.
- **[`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.
- **[`atmos kubernetes render|plan|diff|apply|deploy|delete|validate [--ci]`](/cli/commands/kubernetes/usage)**
  Run Kubernetes operations with a native job summary only. No output variables, status checks, comments, or artifacts are emitted.

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