Skip to main content

Planfile Storage

Planfile storage enables the plan-then-deploy workflow in CI. When configured, atmos terraform plan uploads planfiles to storage, and atmos terraform deploy downloads and verifies them before applying.

Experimental

Configuration

atmos.yaml
components:
terraform:
planfiles:
# Drift verification on `deploy`: fail (default under CI) | warn | off
verify: fail

# Stores are tried in priority order
priority:
- "github"
- "s3"
- "local"

# Named stores
stores:
github:
type: github/artifacts
options:
retention_days: 7

s3:
type: aws/s3
options:
bucket: "my-terraform-planfiles"
prefix: "atmos/"
region: "us-east-1"

local:
type: local/dir
options:
path: ".atmos/planfiles"

Storage Backends

BackendTypeBest For
GitHub Artifactsgithub/artifactsGitHub Actions workflows (recommended)
S3aws/s3AWS-native environments, cross-provider workflows
Locallocal/dirTesting and development
tip

If components.terraform.planfiles is not configured, planfile storage operations are silently skipped. CI summaries and status checks still work without planfile storage.

Using GitHub Artifacts in GitHub Actions

The github/artifacts backend talks to the GitHub Actions Artifacts API directly — Atmos is the native client, not a wrapper around actions/upload-artifact. To do that it needs the runner's runtime credentials, ACTIONS_RUNTIME_TOKEN and ACTIONS_RESULTS_URL.

GitHub injects those into action steps (uses:) but withholds them from run: steps — a deliberate least-privilege decision. So when Atmos's own commands do the upload from a run: step, they will fail with:

GitHub Artifacts upload requires running within GitHub Actions
(ACTIONS_RUNTIME_TOKEN and ACTIONS_RESULTS_URL must be set)

Surface the credentials with the Atmos github-runtime action. With mode: env it exports the credentials to $GITHUB_ENV once, and GitHub then injects them automatically into every later run: step — Atmos's commands pick them up from the environment with no per-step wiring:

.github/workflows/atmos.yaml
steps:
- uses: cloudposse/atmos/actions/github-runtime@v1 # pin to a release or SHA
with:
mode: env

# Credentials are now in the environment of every later run step.
- run: atmos terraform plan mycomponent -s prod --ci # uploads the planfile to github/artifacts
- run: atmos terraform deploy mycomponent -s prod --ci # downloads, verifies, then applies

To keep the credentials out of unrelated steps, use the default mode: output instead: the action emits masked step outputs that you thread, via env:, only into the steps that store planfiles. This is the same github-runtime mechanism the atmos ci cache backend uses — see the cache configuration docs for the full security tradeoff between scoped (mode: output) and ambient (mode: env) credentials.

Least-privilege alternative (mode: output)
steps:
- uses: cloudposse/atmos/actions/github-runtime@v1
id: ghr
- run: atmos terraform plan mycomponent -s prod --ci
env:
ACTIONS_RUNTIME_TOKEN: ${{ steps.ghr.outputs.runtime-token }}
ACTIONS_RESULTS_URL: ${{ steps.ghr.outputs.results-url }}
note

You only need this when Atmos performs the upload from a run: step. The credentials are already present inside uses: action steps, and the local/dir and aws/s3 backends don't use them at all.

How It Works

Plan Phase

When atmos terraform plan runs in CI mode with planfile storage configured:

  1. Terraform generates a binary planfile
  2. The planfile is bundled with the lock file into a tar archive
  3. SHA256 integrity checksums are computed
  4. The bundle is uploaded to the configured storage backend

Deploy Phase

When atmos terraform deploy runs in CI mode with planfile storage configured, verification is automatic:

  1. Downloads the stored planfile bundle from storage
  2. Verifies SHA256 integrity checksums
  3. Generates a fresh plan against current infrastructure
  4. Performs JSON-structural comparison between stored and fresh plans
  5. If plans match: Applies the fresh plan (generated with the apply-time identity and state)
  6. If plans drift: Fails the deploy (mode fail) or warns and proceeds (mode warn)

Atmos reconciles rather than replays: it applies the freshly generated plan once the diff confirms it matches what was reviewed, so the deploy uses the apply-time state and credentials while still guaranteeing no material drift. This avoids the brittleness of a saved plan — which goes stale when the state moves, and whose base credentials come from the apply environment, not the plan (so a plan built on a PR can fail to apply on merge). (To replay the stored binary directly, use --from-plan / --planfile.) Verification runs on deploy only — apply stays a thin wrapper.

GitHub Actions runtime token

With the github/artifacts store, the automatic download (like the upload) talks to the GitHub Artifacts runtime API, so deploy must also run after the github-runtime action surfaces ACTIONS_RUNTIME_TOKEN / ACTIONS_RESULTS_URL — see Using GitHub Artifacts in GitHub Actions above.

Plan Verification

Verification compares the JSON plan structures (terraform show -json) of the stored and fresh plans — not a naive text diff — detecting meaningful changes while ignoring cosmetic noise (sorted keys, masked sensitive values, skipped computed-hash attributes and data sources).

This semantic comparison is deliberate. A plan legitimately contains values that vary between review and apply — attributes "known after apply," computed fields, hashes, ordering, timestamps. A byte-for-byte check (or Terraform's own saved-plan apply, which any state-lineage change invalidates) would reject a plan that is still perfectly valid. Verification needs wiggle room: tolerate benign variation while still catching substantive drift — a resource added, removed, or actually changed.

It is configurable via components.terraform.planfiles.verify:

components:
terraform:
planfiles:
verify: fail # fail (default under CI) | warn | off
  • fail (default under CI): fail the deploy on drift.
  • warn: log the drift but proceed with the fresh plan.
  • off: skip verification (and the stored-plan download).

A companion boolean, components.terraform.planfiles.required, governs whether a stored plan must exist to verify against. It defaults to tracking verify strictness (required when verification resolves to fail), so a fail-by-default CI deploy fails loudly instead of silently applying an unverified fresh plan. See Missing stored plan.

Per-run overrides: --verify-plan (force fail) and --verify-plan=false (force off); the CLI flag beats config, which beats the CI default. See atmos terraform deploy and Planfile drift verification.

CLI Commands

Planfile Commands

Manage stored planfiles with upload, download, list, delete, and show commands.