Autoplan & change detection
Plan only what changed.
A newsroom on press night does not re-proofread the whole paper because one reporter fixed one sentence. The editor re-reads the page that changed, plus every page built from the same shared template. Autoplan is that editor. When a pull request (a proposed set of changes, sitting and waiting for review) opens or picks up a new commit, Atlantis works out which Terraform *projects* the change actually touches, then runs terraform plan for exactly those. One plan per project, gathered into a single comment on the pull request. Nothing else.
The matching logic has a name: change detection. It decides whether Atlantis feels helpful or feels like spam. Get it right and a one-line pull request plans precisely the resources it can affect, its *blast radius*. Get it wrong in one direction and every pull request fires fifty plans. Get it wrong in the other and a risky change merges without ever being planned at all.
How Atlantis decides what changed
The chain starts at the webhook, which is an automatic HTTP call your code host makes the moment something happens. Your VCS (version control system, meaning GitHub, GitLab, Bitbucket and friends) sends a pull_request or push event to the Atlantis /events endpoint. Atlantis checks the HMAC signature (hash-based message authentication code, a short cryptographic stamp proving the message really came from your VCS and nobody edited it in transit) against its webhook secret. Then it asks the VCS API (application programming interface, the machine-readable version of the website) for the list of files that pull request modified. That last detail matters more than it looks. Change detection is not a local git diff against the clone. On GitHub, Atlantis pages through the *List pull request files* API, which returns at most 3,000 files. A monster refactor past that ceiling can quietly drop files from detection. One more reason to keep pull requests small.
What happens next depends on whether the repo carries an atlantis.yaml. Without one, Atlantis falls back to convention: any directory holding a modified file that matches the server's --autoplan-file-list default counts as a project and gets planned where it sits. That default covers **/*.tf, **/*.tfvars, their .json variants, OpenTofu's .tofu equivalents, **/terragrunt.hcl, and **/.terraform.lock.hcl. Fine for a repo with three stacks. In a monorepo (one repository holding many separate stacks) you declare the projects yourself, and each project's when_modified globs (wildcard file patterns, the same idea as typing *.txt at a shell) become the detection rules:
# repo root — when_modified globs are relative to each project's dirversion: 3parallel_plan: true # plan independent projects concurrentlyprojects:- name: app-stagingdir: live/staging/appautoplan:enabled: truewhen_modified:- "**/*.tf"- "**/*.tfvars"- "../../../modules/app/**/*.tf" # re-plan when the shared module changes- name: app-proddir: live/prod/appautoplan:enabled: truewhen_modified:- "**/*.tf"- "**/*.tfvars"- ".terraform.lock.hcl"- "../../../modules/app/**/*.tf"
Two details trip up almost everyone. First, those globs are relative to dir, not to the repo root. That is why ../../../modules/app has to climb three levels out of live/prod/app before it reaches the shared module. Second, hand-writing dependency globs is chore work that goes stale the day somebody adds a module. Recent Atlantis versions can work them out for you. The server flag --autoplan-modules reads the local module blocks of every project the autoplan file list picks up, then re-plans every consumer when one of those modules changes. On a big monorepo, reach for --autoplan-modules-from-projects instead. It takes file patterns the same way the autoplan file list does, limits the module tracing to the projects those patterns match, and per the docs it *overrides* --autoplan-modules when both are set. That keeps Atlantis from indexing every project on every single event.
One pull request, start to finish
Here is the whole loop against the config above. One edited module file, fanning out to both of the projects that use it:
# 1. Change the shared module on a branch and pushgit switch -c app-mod-8443$EDITOR modules/app/security_group.tf # open 8443 from the ALBgit commit -am "app module: allow 8443 from the ALB"git push -u origin app-mod-8443# 2. Open the PR — this fires the pull_request webhookgh pr create --fill# https://github.com/acme-corp/infra/pull/482# 3. Seconds later, Atlantis comments on PR #482:## Ran Plan for 2 projects:# 1. dir: `live/staging/app` workspace: `default`# 2. dir: `live/prod/app` workspace: `default`## ### 1. dir: `live/staging/app` workspace: `default`# Terraform will perform the following actions:# # aws_security_group_rule.app_8443 will be created# Plan: 1 to add, 0 to change, 0 to destroy.## * To apply this plan, comment: `atlantis apply -d live/staging/app`# * To plan this project again, comment: `atlantis plan -d live/staging/app`# (project 2 follows the same format)
Read that comment slowly, because the entire workflow is encoded in it. One edited file produced *two* plans. The module glob fanned the change out to staging and to prod. Each plan is saved on the server, and each one grabbed a lock on its project and workspace pair. A second pull request touching live/prod/app will now refuse to plan until this one merges, closes, or somebody comments atlantis unlock on it. The apply instructions arrive per project, so you can run atlantis apply -d live/staging/app, watch it land safely, and only then go near prod. A bare atlantis plan comment re-runs the same detection from scratch, which is what you want after pushing a fix commit.
Tuning detection once the repo gets big
Autoplan's failure mode at scale is fan-out. A lazy glob like ../../../modules/** matches every module in the tree, not the one you actually import, so a typo fix in a module you have never touched re-plans your project anyway. Multiply that by fifty projects and every pull request becomes a wall of plan comments and a traffic jam of contested locks. Scope each project to the modules it genuinely imports. Sometimes the opposite move is the right one. For a stack where a mistake hurts badly (DNS, the internet's phone book; IAM, the rules saying who may do what; core networking), set autoplan: enabled: false and make somebody type a deliberate atlantis plan -d comment. Two server flags finish the picture. --silence-no-projects stops Atlantis commenting on pull requests where no project matches the changed files, which you will want the moment Atlantis is installed across the whole organization. And a project-level branch: regex (a text-matching pattern) limits a project to pull requests aimed at one base branch.
Those knobs live on the server, so bake them into your deployment. On Kubernetes, the official Helm chart runs Atlantis as a StatefulSet (a workload type that keeps the same identity and the same disk across restarts), because pending plans are state. They sit on a persistent volume in the gap between the plan comment and the apply:
# official chart: helm repo add runatlantis https://runatlantis.github.io/helm-chartsorgAllowlist: github.com/acme-corp/*github:user: atlantis-botvcsSecretName: atlantis-vcs # existing Secret with github_token + github_secretextraArgs:- --autoplan-modules-from-projects=live/**/*.tf # trace local module deps for these# projects only (overrides --autoplan-modules)- --silence-no-projects # no comment spam on non-Terraform PRs# Deploy / upgrade:# helm upgrade --install atlantis runatlantis/atlantis -n atlantis -f values.yaml# kubectl -n atlantis rollout status sts/atlantis# -> statefulset rolling update complete 1 pods at revision atlantis-7d9f65c...
Notice the split, because it is a trust boundary you will meet again. when_modified is repo-side config, and any pull request author can edit it. The flags above are server-side, and only platform admins can touch them. Autoplan tuning is safe to hand over, since the worst a bad glob does is plan too much or plan too little. Stay alert the moment a repo-side key starts deciding *what runs* rather than *what triggers*.
When autoplan stays silent
Autoplan quietly not firing is the most common Atlantis support ticket, and the causes repeat themselves. Work through them in order. The webhook has to be subscribed to every event Atlantis listens for. On GitHub the docs say to tick Pull requests, Pushes, Issue comments, and Pull request reviews. Miss any one of them and the failure is equally quiet. Leave out *Issue comments*, for example, and a typed atlantis plan comment does nothing at all. GitHub *draft* pull requests are skipped unless the server runs --allow-draft-prs. A glob written from the repo root, like live/prod/app/*.tf, inside a project whose dir is already live/prod/app, matches nothing. And editing atlantis.yaml itself triggers no plans unless some glob covers that file. Deleted files, in case you were wondering, do count as modified; they come back in the VCS API's file list like any other change. The server logs settle whatever argument is left:
# Tail the server while pushing a commit to the PRkubectl -n atlantis logs sts/atlantis -f | grep -Ei "modified|projects"# {"level":"debug","msg":"3 files were modified in this pull request. Modified files: [...]","pull":"482"}# {"level":"info","msg":"2 projects are to be planned based on their when_modified config","pull":"482"}# (the per-file line only appears with --log-level debug; the projects line logs at info)# Nothing at all? The event never arrived — check the webhook first:# GitHub repo -> Settings -> Webhooks -> Recent Deliveries# 200 from POST /events = Atlantis received it; 400 = webhook-secret mismatch
when_modified list has no pattern pointing at that module's path. Change the module and the project's plan never runs. Nothing complains. The pull request goes green, it merges, and the drift shows up weeks later when some unrelated plan suddenly wants to change resources nobody remembers touching. Map the dependencies exhaustively in when_modified, or let --autoplan-modules derive them for you, remembering that it only traces *local-path* module sources. Registry and git-pinned modules are covered anyway, because taking a new version of one means editing a version string in the consumer's own .tf file. An incomplete pattern gives you a silently unplanned change, and that is strictly worse than a noisy over-plan.Autoplan turns a diff into a set of plans. Which raises the obvious next question: who, or what, gets to judge those plans before atlantis apply is allowed to run? That job belongs to automated judges, OPA (Open Policy Agent) and Conftest reading every plan autoplan produces. The next lesson, *Policy checks in the flow*, covers exactly that.
Shared modules are where teams get blindsided. A one-line change to a default value can alter thirty stacks, while autoplan runs only the one stack that happened to edit a .tf inside its own directory. Write the dependency globs out explicitly instead of hoping. Plenty of teams also run a nightly full plan somewhere else to catch drift, and leave autoplan focused on the pull request in front of it.
Turn autoplan off for the giant directories that always time out, and require a typed atlantis plan comment there instead. Silence beats a twenty-minute webhook that flakes half the time and teaches everyone to ignore the status checks.
Put a short "what autoplans what" page in the repo: project name, dir, and the globs that wake it up. A new contributor should not have to reverse-engineer atlantis.yaml to find out whether editing modules/network will plan prod. Slightly stale docs still beat knowledge that lives in one platform engineer's head.
Try this
Edit a shared module file and a leaf stack file in two separate commits. Then check whether your when_modified rules pull in the dependent project or skip it, and whether that matches what you meant to happen.
git diff --name-only origin/main...HEAD# expect Atlantis to plan projects whose when_modified globs match# tune atlantis.yaml when_modified, push, read the PR comment header
Files changed:modules/vpc/main.tfstacks/prod/network/main.tfRan Plan for 2 projects: network-prod, network-stage# if dependents missing, widen when_modified; if unrelated stacks appear, narrow it
Takeaway
Autoplan is change detection wearing a friendly name. Match your globs to the real blast radius and it feels magical. Miss, and you get either spam or silence. Silence is the one that hurts.
Next: write your module dependency paths into when_modified, and treat "0 projects" on a Terraform pull request as an alarm rather than a relief.
app-prod imports the shared local module at modules/app, but its when_modified list stops at ["**/*.tf", "**/*.tfvars"] and the server was never given --autoplan-modules. Someone opens a pull request that edits only modules/app/security_group.tf. What shows up on that pull request?--silence-no-projects will even suppress the comment that might otherwise have tipped you off.../../../modules/**.--autoplan-modules is half-finished because it traces only local-path module blocks, and should follow registry sources and git-ref-pinned sources too. What is the strongest answer?atlantis.yaml above, both app-staging and app-prod carry the glob ../../../modules/app/**/*.tf. A pull request edits only modules/app/security_group.tf, and the server log line reads 2 projects are to be planned based on their when_modified config. What follows?../ segments exist precisely so the pattern can reach out to the shared module. Both projects match.atlantis unlock.