GitHub Actions Workflows
Create the GitHub Actions workflows that Atmos Pro dispatches to plan, apply, and detect drift on your infrastructure.
Atmos Pro orchestrates your deployments by dispatching dedicated GitHub Actions workflows that you define for Atmos Pro. For security, Atmos Pro only dispatches workflows against your repository's default branch. This ensures your branch protection rules guard against manipulation — workflows must be merged before they take effect.
You'll create the following workflows in your repository:
- 1Affected Stacks — Runs on every pull request to detect which stacks changed
- 2Plan — Dispatched by Atmos Pro to run
atmos terraform planfor each affected stack - 3Apply — Dispatched by Atmos Pro when a plan is approved
- 4Upload Instances — Runs on merge to the default branch and on a schedule to power drift detection
The workflows below are single-purpose Atmos Pro workflows. Each one runs the relevant Atmos command with
--upload so the command reports its result to Atmos Pro.For a working Atmos Pro repository, configure these four workflow files plus the matching
settings.pro stack configuration:| File | Trigger | Command | Why it matters |
|---|---|---|---|
.github/workflows/atmos-pro.yaml | pull_request, merge_group | atmos describe affected --upload | Uploads affected stack/component pairs so Atmos Pro knows what to plan or apply. |
.github/workflows/atmos-terraform-plan.yaml | workflow_dispatch | atmos terraform plan --upload | Runs plans that Atmos Pro dispatches for affected stacks and drift checks. |
.github/workflows/atmos-terraform-apply.yaml | workflow_dispatch | atmos terraform deploy --upload | Runs approved applies with GitHub Environment protection. |
.github/workflows/atmos-pro-upload-instances.yaml | push, schedule, workflow_dispatch | atmos list instances --upload | Uploads inventory so Stacks, Instances, and drift detection know what exists. |
The affected stacks workflow is the entry point. It runs on pull request events and reports the result of
atmos describe affected --upload to Atmos Pro. Atmos Pro then reads each affected component/stack pair, resolves dependencies from your stack configuration, and dispatches the configured plan or apply workflows.The plan and apply workflows must be
workflow_dispatch workflows because Atmos Pro calls them directly. Their workflow inputs must match the inputs configured under settings.pro in your Atmos stacks. At minimum, plan needs sha, component, and stack; apply needs those plus github_environment if you use GitHub deployment approvals.The upload instances workflow is separate from affected stacks. It should run after changes merge to the default branch and on a schedule, because it keeps Atmos Pro's inventory current even when no pull request is open.
plan-wf-config: &plan-wf-config
atmos-terraform-plan.yaml:
inputs:
component: "{{ .atmos_component }}"
stack: "{{ .atmos_stack }}"
drift-detection-wf-config: &drift-detection-wf-config
atmos-terraform-plan.yaml:
inputs:
component: "{{ .atmos_component }}"
stack: "{{ .atmos_stack }}"
apply-wf-config: &apply-wf-config
atmos-terraform-apply.yaml:
inputs:
component: "{{ .atmos_component }}"
stack: "{{ .atmos_stack }}"
github_environment: "{{ .vars.stage }}"
settings:
pro:
enabled: true
drift_detection:
enabled: true
detect:
workflows: *drift-detection-wf-config
remediate:
workflows: *apply-wf-config
pull_request:
opened:
workflows: *plan-wf-config
synchronize:
workflows: *plan-wf-config
reopened:
workflows: *plan-wf-config
merged:
workflows: *plan-wf-config
merge_group:
checks_requested:
workflows: *apply-wf-configThis mixin assumes you use GitHub merge queue and keeps each lifecycle explicit: PR updates plan, the queue commit applies, and the merged PR lifecycle runs a post-merge plan/verification workflow. The command each lifecycle dispatches is your policy decision; the important rule is that every lifecycle that uploads affected stacks has matching
settings.pro workflows. If your affected-stacks workflow uploads on pull_request.closed with merged == true, configure pull_request.merged. If that lifecycle should not run, skip atmos describe affected --upload for merged PR events instead. See Configure Atmos Stacks for the full lifecycle model.For more detail on the stack configuration, see Configure Atmos Stacks. For the inventory workflow, see Upload Instances.
Don't use concurrency groups
Avoid adding
concurrency: to workflows that Atmos Pro dispatches (plan, apply, upload instances, drift detection).
By default GitHub Actions keeps one run executing and one run pending per concurrency group, and a third arrival
replaces the pending run. The currently executing run is only cancelled when cancel-in-progress: true is set. GitHub
also supports queue: max for FIFO queuing of up to 100 pending runs. We still recommend skipping concurrency: on
Atmos Pro–dispatched workflows because Atmos Pro already serializes per stack+component, and layering GitHub-side
concurrency on top creates confusing queue/cancel behavior and risks terraform plan/apply being interrupted —
leaving state locks and partial work behind.This workflow runs on every pull request event against the default branch (for example,
main). It uses atmos describe affected --upload to determine which stacks changed and report them to Atmos Pro.name: 👽 Atmos Pro
run-name: 👽 Atmos Pro
on:
pull_request:
types:
- opened
- synchronize
- reopened
- closed
branches:
- main
merge_group:
permissions:
id-token: write
contents: read
checks: write
jobs:
affected:
name: Trigger Affected Stacks
runs-on:
- "ubuntu-latest"
container:
image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}
defaults:
run:
shell: bash
if: |
github.event_name == 'merge_group' ||
(!contains(github.event.pull_request.labels.*.name, 'no-apply') &&
(github.event.action != 'closed' || (github.event.action == 'closed' && github.event.pull_request.merged == true)))
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: Configure Git Safe Directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Determine Affected Stacks
env:
ATMOS_PRO_WORKSPACE_ID: ${{ vars.ATMOS_PRO_WORKSPACE_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ATMOS_PROFILE: github
run: |
atmos describe affected --uploadThe
merge_group trigger lets Atmos Pro participate in GitHub's merge queue. When a PR enters the queue, GitHub fires merge_group once on the synthetic merge SHA in a gh-readonly-queue/<base>/pr-<N>-<sha> branch — atmos describe affected --upload re-runs against that SHA, and Atmos Pro reports a fresh "Atmos Pro" check on it. Without merge_group in this workflow's on: block, the queue's required-status-check enforcement on Atmos Pro will sit stuck on "Expected — Waiting for status to be reported".Merge queue does not replace the merged PR lifecycle. With the
closed trigger and if: guard below, this workflow also uploads affected stacks after the PR actually merges. That means settings.pro.pull_request.merged.workflows must exist for every component/stack instance that can be affected by the merged upload, or the workflow should skip atmos describe affected --upload for merged PR events.The
if: guard now also lets merge_group events through because github.event.pull_request is absent for queue runs, so the original PR-only condition would short-circuit them.Notice that
atmos describe affected --upload is called without --sha or --ref. With ci.enabled: true in your atmos.yaml, Atmos resolves the right base SHA per event (pull_request, pull_request_target, merge_group, push) from GITHUB_EVENT_NAME and GITHUB_EVENT_PATH. Older templates that pass --sha ${{ github.event.merge_group.base_sha }} or --sha ${{ github.event.pull_request.base.sha }} aren't wrong, just redundant — and they break the moment a new event type is added.Scope uploads before dispatching broad lifecycles
Every affected component/stack reported by
atmos describe affected --upload is evaluated against the matching lifecycle in settings.pro. If a queue or merge lifecycle dispatches apply, a PR that touches a defaults mixin imported by every stack can fan apply across every reported stack. Add a --stack filter for the lifecycle branch you want to constrain, or explicitly choose multi-stack dispatch. See Common pitfalls when enabling merge queue on the stack configuration page.See GitHub Merge Queue in the stack configuration reference for the matching
settings.pro.merge_group block.Every workflow above pulls the Atmos container image at
ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }}. Set ATMOS_VERSION as a GitHub Actions variable so all workflows pin to the same release and you can roll forward in one place.No leading `v`
Docker image tags don't use the
v prefix. Set the value to 1.175.0 (or whichever release you want to
pin), not vX.Y.Z — the container pull will 404 otherwise.Run
gh variable set from a checkout of the repo (or from anywhere with --repo). The snippets below have the latest Atmos release baked in — copy and run.Repository scope:
gh variable set ATMOS_VERSION --body "1.175.0"Organization scope — pin one Atmos version across every repo:
gh variable set ATMOS_VERSION --org my-org --visibility all --body "1.175.0"Verify:
# Repository scope
gh variable get ATMOS_VERSION
# Organization scope
gh variable get ATMOS_VERSION --org my-orgAll workflows use GitHub OIDC to authenticate with the Atmos Pro API, eliminating the need for long-lived API tokens. Workflows run wherever your GitHub Actions run, whether on GitHub-hosted runners or your own self-hosted runners.
Atmos uses Auth to manage how the CLI obtains cloud credentials. Because you typically have different identities depending on context — OIDC in CI versus single sign-on locally — we recommend using profiles. Profiles let you define separate auth configurations for each context (e.g.,
profiles/github/atmos.yaml for GitHub Actions, profiles/local/atmos.yaml for local development).In your workflows, set the
ATMOS_PROFILE environment variable to load the right profile:env:
ATMOS_PROFILE: githubThe plan and apply workflows use
workflow_dispatch so Atmos Pro can trigger them at the right time in the right order. Atmos Pro passes the following inputs:sha(required)- The commit SHA to check out. Since
workflow_dispatchdoesn't have pull request context, Atmos Pro passes the SHA explicitly. component(required)- The Atmos component to run the workflow on.
stack(required)- The Atmos stack to run the workflow on.
github_environment(required for apply)- The GitHub Environment to use for the workflow run. This ties into GitHub's native approval gates — configure environment protection rules to require manual approval before applies run. We recommend defining environments along permission boundaries, typically by stage (e.g.,
dev,staging,production), the same way you separate cloud accounts or roles. Atmos Pro reads this value from your stack configuration and passes it automatically.
Each workflow YAML file must declare a
permissions block so the GitHub Actions runner token has the access it needs:permissions:
id-token: write # Required for OIDC token generation
contents: read # Required for checking out code
checks: write # Required for creating/updating GitHub Checks
statuses: write # Required for posting commit status contextsThe
checks and statuses permissions are needed because the Atmos CLI writes status checks and commit statuses directly from these Atmos Pro workflow runs — for example, reporting how many resources a Terraform plan will create, update, or destroy. Atmos Pro orchestrates when workflows run, and each workflow reports the command result back through --upload.The
ATMOS_PRO_WORKSPACE_ID variable tells Atmos Pro which workspace a pull request or event belongs to. Set it as a GitHub Actions variable in your repository or organization settings. Your current workspace ID is ws_xxxxxxxxxxxxxxxxxxxxxxxxxxxx — you can always find this in the Workspace Settings page. The GITHUB_TOKEN is automatically provided by GitHub Actions.Here's how it looks in your workflow YAML — reference the variable using
vars.ATMOS_PRO_WORKSPACE_ID:env:
ATMOS_PRO_WORKSPACE_ID: ${{ vars.ATMOS_PRO_WORKSPACE_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ATMOS_PROFILE: githubThe
env block in the workflows is a GitHub Actions workflow env
block, not the Atmos
env configuration section. It sets environment variables for the runner so the Atmos CLI can authenticate with Atmos
Pro.Atmos Pro uses the workflow filename to determine what command each deployment represents. This lets the dashboard show the correct badge and filter deployments by command type.
Recommended workflow filenames:
| Command | Filename | Description |
|---|---|---|
| Affected | atmos-pro.yaml | Runs atmos describe affected --upload |
| Plan | atmos-terraform-plan.yaml | Runs atmos terraform plan --upload |
| Apply | atmos-terraform-apply.yaml | Runs atmos terraform deploy --upload |
| Upload Instances | atmos-pro-upload-instances.yaml | Runs atmos list instances --upload |
Atmos Pro checks if the filename contains keywords like
plan, apply, affected, or instances (case-insensitive). If the filename doesn't contain a recognizable keyword, the command is determined later when the Atmos CLI reports back the actual command that was executed.The workflow filename is a best-effort heuristic. The canonical command comes from the Atmos CLI callback after the
workflow runs. If the CLI reports a different command than what the filename suggests, the CLI value takes precedence.
You may have noticed how small these workflows are — that's by design. They are dedicated Atmos Pro entry points, and Atmos Native CI handles the supporting CI behavior around the commands. Out of the box, Native CI takes care of:
- Toolchain installation — Atmos, Terraform/OpenTofu, and dependencies
- Authentication — OIDC token exchanges and cloud credential management
- Terraform backend provisioning — Automatic setup of state storage
- Status checks — Reporting plan/apply results back to GitHub
- Job summaries — Rich workflow run summaries in GitHub Actions
Ready to get started?
Install the Atmos Pro GitHub App to get started, or explore a complete working example.