Skip to main content

Native CI

Atmos integrates CI/CD directly into the CLI. Run Terraform commands in GitHub Actions to publish job summaries, status checks, output variables, and pull-request plan comments. Use stored planfiles to verify changes before applying, and Git-aware commands to plan and deploy only the components affected by a change.

Experimental
Native CI replaces legacy Terraform actions

Atmos now handles Terraform CI directly, replacing the legacy Terraform GitHub Actions. Use native Atmos commands for new workflows.

Existing workflows that use the legacy actions continue to work. See GitHub Actions Workflows for examples and setup requirements.

Quick Start​

atmos.yaml
ci:
enabled: true
summary:
enabled: true
output:
enabled: true
variables:
- has_changes
- has_additions
- has_destructions
- plan_summary
checks:
enabled: true
comments:
enabled: true
behavior: upsert

Use this configuration with the GitHub Actions workflows below.

CI Configuration

Configure CI providers, job summaries, output variables, status checks, pull-request plan comments, planfile storage, and templates in your atmos.yaml.

GitHub Actions Workflows​

Run Atmos directly in GitHub Actions to plan and deploy infrastructure.

The examples run in the Atmos container, which supplies the CLI. Set the ATMOS_VERSION repository variable to a specific Atmos release; there is no latest tag. Replace vpc and prod with your component and stack names.

Use the CI configuration in Quick Start and configure Authentication for your infrastructure. The plan and apply workflows below include the permissions and GitHub token needed for CI reporting.

Plan on Pull Request​

.github/workflows/plan.yml
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Apply on Merge​

.github/workflows/apply.yml
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

atmos terraform deploy runs a fresh plan and applies it with -auto-approve. When 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 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 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.

.github/workflows/deploy-affected.yml
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.

.github/workflows/deploy-all.yml
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"

Gating Production with Environments​

GitHub Actions environments 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:

.github/workflows/apply.yml
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
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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.

Concurrency groups aren't a deployment queue

By default (concurrency.queue: single), a GitHub Actions concurrency group holds at most two runs at a time: one in progress and one pending. Triggering a third run cancels the pending one and takes its place — this eviction happens regardless of cancel-in-progress, so intermediate triggers can be silently dropped. Setting cancel-in-progress: true additionally cancels the in-progress run itself, which can interrupt a Terraform apply and leave a remote state lock that needs manual recovery. Setting queue: max instead allows up to 100 pending runs before any get canceled, but it is still not a FIFO deployment queue and cannot be combined with cancel-in-progress: true.

Remote state locking only prevents concurrent writers — it doesn't protect against a partially applied change or automatically recover an interrupted run. Inspect the affected resources, confirm the previous run has actually stopped, and only then run atmos terraform force-unlock before retrying the apply. GitHub environments add approval gates and merge queues order merges, but neither guarantees deployment execution order — reach for an explicit promotion workflow or deployment controller when you require that.

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

Workflow Setup and Reference​

Permissions​

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

FeaturePermission
Job summaries ($GITHUB_STEP_SUMMARY)none required
Output variables ($GITHUB_OUTPUT)none required
Commit status checksstatuses: write
Check runs (modern Checks API)checks: write
PR commentspull-requests: write
Checkoutcontents: read
OIDC token issuanceid-token: write
SBOM workflow artifact uploadcontents: read; GitHub Actions runtime credentials (see SBOM Artifacts)

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.

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. The profile name is arbitrary; we use github to match the example repos. It holds the github/oidc provider plus the identity CI should use:

atmos.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 for the trust-policy template. Other clouds work the same way: see azure/oidc and gcp/workload-identity-federation.

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

.github/workflows/apply.yml
permissions:
id-token: write # Required for GitHub to mint the OIDC token
contents: read
statuses: write
checks: write

env:
ATMOS_PROFILE: github
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

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

For deeper auth reference: Profiles, Auth concepts, Providers, Identities.

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.

.github/workflows/plan.yml
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:

atmos.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 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 for keys, paths, and the four GitHub Actions integration options with their security trade-offs.

SBOM Artifacts​

Running atmos sbom generate --upload uses the detected native CI provider to retain the generated SBOM with the CI run. In GitHub Actions, this is a workflow artifact, not a Dependency Graph import. GitHub's SBOM REST API can export or request a GitHub-generated SPDX report, but it does not accept an arbitrary submitted SBOM. Add github-runtime before the command so Atmos can access the runtime API for Actions artifacts.

- uses: cloudposse/atmos/actions/github-runtime@v1
with:
mode: env
- run: atmos sbom generate --format spdx-json --output sbom.spdx.json --upload
env:
GITHUB_TOKEN: ${{ github.token }}

Validate Workflows​

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

.github/workflows/validate.yml
- name: Validate workflows
run: atmos validate ci

atmos validate ci is an alias for atmos 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:

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 for file selection, output formats, and configuration details.

Features​

Terraform commands support the full native CI feature set below. Kubernetes commands currently produce job summaries only; they do not emit output variables, status checks, PR comments, or stored artifacts.

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​

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​

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.

PR Comments​

When ci.comments.enabled: true, Terraform plans running in a pull-request context can post their rendered plan summary as a PR comment. The comment uses the same template as the job summary and, with the default upsert behavior, updates one comment per command, component, and stack on later runs. This requires GitHub Actions pull-requests: write; comments are not posted for non-PR runs, when no summary is available, or by apply, deploy, Kubernetes, Helm, or Helmfile commands.

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​

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

GitOps​

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 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 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 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, the atmos git 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]
Run plan with job summary, output variables, status checks, and planfile upload.
atmos terraform apply [--ci]
Run apply with job summary, output variables, and status checks.
atmos terraform deploy [--ci]
Deploy with stored planfile verification, drift detection, and full CI reporting.
atmos helm template|diff|apply|deploy|delete [--ci]
Run native Helm components with job summaries for rendered/applied object metadata.
atmos helmfile template|diff|apply|sync|deploy|destroy [--ci]
Run Helmfile components with job summaries that include captured masked command output.
atmos terraform planfile
Manage stored planfiles: upload, download, list, delete, and show.
atmos describe affected --format=matrix
Generate GitHub Actions matrix strategy from affected components.
atmos kubernetes render|plan|diff|apply|deploy|delete|validate [--ci]
Run Kubernetes operations with a native job summary only. No output variables, status checks, comments, or artifacts are emitted.
atmos vendor update --pull-request
Open (or update) a pull request with available vendored-component updates directly from CI — replaces the deprecated Component Updater action.

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.