Git, CI/CD & GitOpsLXXXVIII · Infrastructure GitOpsFoundations
Terraform and GitOps with Atlantis — pull-request-driven plan and apply
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
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
- Force all applies through Atlantis. A direct
terraform applyfrom 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. - 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.
- 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
planartefact, which Atlantis comments on the PR.
Quiz
Knowledge check · 4 questions
Q1. Two pull requests are opened at the same time against the same Terraform workspace. What does Atlantis do?
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.
Q3. What is the canonical approval comment that unlocks an Atlantis apply, and what protects against an approval against a stale plan?
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.