Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXXXVIII · Infrastructure GitOpsFoundations

Terraform and GitOps with Atlantis — pull-request-driven plan and apply

Advanced⏱ ~26 mingitterraform

What you'll learn

  • Trace a pull request through Atlantis from webhook to plan to apply
  • Distinguish the atlantis plan command from the atlantis apply command and the approval boundary between them
  • Explain why Atlantis uses Terraform state locking to serialise concurrent plans
  • Recognise the operational limits of Atlantis: not a continuous reconciler, a pull-request loop

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

Atlantis is the most widely deployed Terraform GitOps tool in production. It is not a controller in the Kubernetes sense: it runs as a server that listens for pull-request webhooks from GitHub, GitLab, Bitbucket, or Azure DevOps, executes terraform plan against the changed directories, and posts the diff as a comment on the pull request. When a reviewer approves the plan by typing a comment, Atlantis runs terraform apply. The state file stays in the configured backend (S3, Terraform Cloud, GCS); Atlantis never holds state, only locks it for the duration of a plan or apply.

The model is GitOps-shaped. The desired state is the HCL files in Git. The reconciler is Atlantis, hosted outside the cluster because the cluster does not exist for Terraform. The loop is per-pull-request rather than per-tick, which is the operational difference from Kubernetes GitOps and the source of most of Atlantis’s production limits.

sequenceDiagram
    participant Dev as Engineer
    participant GH as Git host
    participant A as Atlantis
    participant S as State backend
    participant C as Cloud
    Dev->>GH: open PR
    GH->>A: webhook
    A->>S: acquire lock
    A->>C: terraform plan
    A->>GH: comment diff
    Dev->>GH: review and approve
    GH->>A: webhook
    A->>S: acquire lock
    A->>C: terraform apply
    A->>GH: comment result
    A->>S: release lock

The two commands that drive the loop are atlantis plan and atlantis apply. The first is invoked by Atlantis on every PR push and every comment; the second is invoked only after an explicit approval comment, which is the human boundary that prevents a misclick from applying a misconfigured plan.

The atlantis plan command

atlantis plan is the reconcile step. It acquires the state lock, runs terraform plan against the working directory of the pull request, posts the plan output as a comment on the PR, and releases the lock. The lock is per workspace, not per pull request: two PRs touching the same Terraform workspace will serialise, with the second plan waiting for the first to finish.

The plan output is the contract. Reviewers do not see the HCL diff in isolation; they see the rendered plan - “aws_vpc.main will be created”, “aws_route_table.private will be updated in place”, “aws_iam_role.deployer will be destroyed and recreated”. That rendered plan is what they approve, and what they approve is what applies. Drift between the approved plan and the actual apply is what the next lesson on limitations covers.

atlantis plan \
  --dir infra/network \
  --workspace production

Atlantis accepts a directory and a workspace. The directory is the path relative to the repo root where terraform plan runs. The workspace is the Terraform workspace that selects the state file in the backend. A single repo can host many workspaces (dev, staging, production), and each workspace runs its own plan against its own state lock.

The atlantis apply command

atlantis apply is the convergence step. It runs only after a reviewer types an approval comment on the PR - the default keyword is atlantis apply itself, configurable per repo. Atlantis re-runs the plan, verifies the diff has not changed since the comment was posted, and then applies the diff. The re-plan-then-apply check is what protects against a stale approval: if another PR has merged and changed the state since the reviewer’s comment, the apply aborts and the reviewer must re-approve against the new plan.

atlantis apply \
  --dir infra/network \
  --workspace production

The apply runs from the locked state context. Atlantis holds the lock for the duration of the apply, the cloud provider sees a single transaction per workspace, and the resulting state is written back to the backend atomically. If the apply fails halfway through, the partial state is recorded and the next plan surfaces the partial state as drift. The recovery is not an automatic rollback; it is a new plan that names what is still missing.

The approval boundary

The approval boundary is the production discipline. Atlantis will not apply on a single reviewer’s approval of the diff in isolation; it requires an explicit comment on the PR with the configured keyword. The comment is logged in the PR history and visible to every reviewer, which means the audit trail links the cloud change to a specific human-typed approval at a specific commit.

The boundary fails in two ways. The first is the “drive-by-approval”: a reviewer approves a plan without reading it because the diff is small or the engineer’s history is trusted. The second is the “stale-approval”: a reviewer approved an earlier commit, a force-push changed the plan, and Atlantis did not catch it because the re-plan-then-apply check matched a hash the new plan still produced.

Production discipline

  1. Force all applies through Atlantis. A direct terraform apply from a laptop bypasses the approval boundary and the state lock. The state backend’s last-modified timestamp is the tripwire; alert on applies that did not come through a PR.
  2. Pin the plan output. Atlantis comments include a hash of the plan. The apply must match that hash. If it does not, the apply is rejected. Configure the strictest mode.
  3. Workspace-aware reviewers. A reviewer for the production workspace must not be a reviewer for the dev workspace. The codeowners file should map directories and workspaces to reviewers, and Atlantis should reject applies from the wrong reviewer.

Cross-course references

  • This course, Part LXXXVIII-01 (What infra GitOps is) - the model that Atlantis implements for Terraform.
  • Terraform for Production Sysadmins - Parts IX-XII (State) cover the state backend and locking that Atlantis depends on.
  • This course, Part L (Terraform CI discipline) - the earlier parts of this course cover the plan artefact, which Atlantis comments on the PR.

Quiz

Knowledge check · 4 questions

  1. Q1. Two pull requests are opened at the same time against the same Terraform workspace. What does Atlantis do?

  2. Q2. Atlantis acts as a continuous reconciler like Argo CD, polling Git on a tick and applying drift whenever the live state diverges from the declared state.

  3. Q3. What is the canonical approval comment that unlocks an Atlantis apply, and what protects against an approval against a stale plan?

  4. Q4. Diagnose what went wrong in the Atlantis workflow and identify the missing control.

    Engineer A opens a PR that adds a new security group rule. Atlantis plans and posts a small diff - one rule added. Engineer B reviews and types `atlantis apply` in the PR comment. Engineer A force-pushes a commit that, due to a YAML templating bug, also modifies the production IAM role trust policy. Atlantis re-runs the plan, the apply proceeds, and the IAM trust policy is updated. The reviewer never saw the IAM change because the approval comment was already in place.

Passing score: 75%. Answers are checked in this browser.