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.

The one exception: explicitly requesting verification with --verify-plan (or ATMOS_TERRAFORM_VERIFY_PLAN=true) errors when planfile storage is not configured — there is no stored plan to verify against, and the flag cannot stand in for the storage settings it depends on.

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.