Atlantis
Atlantis is a self-hosted application that runs Terraform plan and apply from your pull requests. Instead of running Terraform locally and pasting plan output into a PR, your team comments atlantis plan and atlantis apply directly on the PR, and Atlantis posts the results back as comments. This keeps every infrastructure change reviewable, auditable, and gated behind your normal code-review process — which is why platform teams adopt it for collaborative Terraform at scale.
How Atlantis works
Atlantis runs as a long-lived server (a container, an EC2 instance, or a Kubernetes deployment) that receives webhooks from your VCS provider — GitHub, GitLab, Bitbucket, or Azure DevOps. When a pull request touches Terraform files, Atlantis clones the branch, detects affected projects, and runs a plan automatically. Reviewers see the exact plan output inline, approve the PR, then trigger the apply with a comment. Because Atlantis executes Terraform server-side, the credentials never leave your infrastructure and developers don’t need cloud admin access on their laptops.
The core loop is:
- A developer opens a PR that modifies
.tffiles. - Atlantis receives the webhook and runs
terraform plan, posting the output as a PR comment. - Atlantis locks the affected directory/workspace so no other PR can plan or apply it concurrently.
- Reviewers approve. The author comments
atlantis apply. - Atlantis runs
terraform apply, posts the result, and releases the lock when the PR is merged or closed.
Atlantis is fully compatible with OpenTofu — set
--default-tf-distribution=opentofu(ordefault_tf_distribution: opentofuin server config) and Atlantis will runtofuinstead ofterraformwith identical comment commands.
PR comment commands
You interact with Atlantis entirely through PR comments. The most common commands:
| Command | Purpose |
|---|---|
atlantis plan | Plan all changed projects in the PR |
atlantis plan -d infra/prod | Plan a specific directory |
atlantis plan -p network | Plan a named project from atlantis.yaml |
atlantis apply | Apply all planned projects (requires approval) |
atlantis apply -d infra/prod | Apply a single directory |
atlantis unlock | Manually release locks held by the PR |
atlantis help | List available commands |
Configuring a repository
Project-level behavior is defined in an atlantis.yaml file at the repo root. This is where you declare projects, autoplan rules, and custom workflows.
version: 3
automerge: true
projects:
- name: network
dir: infra/network
workspace: default
terraform_version: v1.9.5
autoplan:
when_modified: ["*.tf", "../modules/**/*.tf"]
enabled: true
apply_requirements: [approved, mergeable]
- name: prod
dir: infra/prod
terraform_version: v1.9.5
apply_requirements: [approved, mergeable, undiverged]
The apply_requirements field enforces guardrails: approved blocks apply until a reviewer approves, mergeable blocks until CI checks pass, and undiverged blocks if the branch is behind the base.
The Terraform code itself is unchanged — Atlantis runs your normal configuration. A typical project might manage an S3 backend and a bucket:
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "devcraftly-tfstate"
key = "prod/network.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "prod-vpc"
}
}
Example PR flow
When the PR opens, Atlantis autoplans and comments back:
Output:
Ran Plan for project: network dir: infra/network workspace: default
Terraform will perform the following actions:
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ cidr_block = "10.0.0.0/16"
+ enable_dns_hostnames = true
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
To apply this plan, comment:
atlantis apply -d infra/network
After approval, the author comments atlantis apply:
Output:
Ran Apply for project: network dir: infra/network workspace: default
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 2s [id=vpc-0a1b2c3d4e5f6a7b8]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Locking and concurrency
Atlantis maintains its own directory/workspace locks separate from your Terraform state lock. When a PR plans infra/prod, that path is locked until the PR is applied and merged or the lock is released. A second PR touching the same path will be told the project is locked and shown a link to the blocking PR. This prevents the classic race where two engineers apply conflicting plans against the same state. State-file locking (via the DynamoDB table above) remains a second, lower-level safety net.
Running the server
You can run Atlantis from its official image. It needs VCS credentials and a webhook secret to validate incoming payloads.
docker run -p 4141:4141 ghcr.io/runatlantis/atlantis:latest \
server \
--gh-user="$ATLANTIS_GH_USER" \
--gh-token="$ATLANTIS_GH_TOKEN" \
--gh-webhook-secret="$ATLANTIS_WEBHOOK_SECRET" \
--repo-allowlist="github.com/devcraftly/*"
Always set
--repo-allowlistto an explicit pattern. Without it Atlantis will refuse to start, and a too-broad allowlist (*) lets any repo trigger Terraform runs against your cloud credentials.
Best practices
- Set
apply_requirements: [approved, mergeable](andundivergedfor production) so no infrastructure changes apply without review and green CI. - Scope
--repo-allowlistnarrowly and store the webhook secret so Atlantis rejects forged payloads. - Use
autoplan.when_modifiedto include shared module paths, so a module change replans every dependent project. - Pin
terraform_version(or the OpenTofu equivalent) per project to keep plans deterministic across upgrades. - Give Atlantis a dedicated, least-privilege cloud role rather than reusing a developer’s credentials.
- Enable
automergeonly once yourapply_requirementsand CI gates are trustworthy, to avoid auto-merging unreviewed changes.