Mapping Branches to Environments
A GitOps model only works if there is a clear, enforced mapping between Git branches (or directories) and real environments. The two most common patterns are:
Pattern 1: Directory-per-environment (monorepo)
infrastructure/
├── environments/
│ ├── dev/
│ │ └── main.tf # Atlantis project: dev
│ ├── staging/
│ │ └── main.tf # Atlantis project: staging
│ └── prod/
│ └── main.tf # Atlantis project: prod
└── modules/
└── rds/
└── main.tf # Shared module
All environments live in one repository. Promotion means opening a PR that copies a configuration value (e.g., an AMI ID or image tag) from environments/staging/main.tf to environments/prod/main.tf. Atlantis plans each directory independently.
Pattern 2: Branch-per-environment
main → production
staging → staging environment
dev → development environment
Changes flow upward: merge to dev, test, then open a PR to staging, test, then open a PR to main. Each branch merge triggers Atlantis or Flux to apply to the corresponding environment.
The directory-per-environment pattern is generally preferred for infrastructure because it makes the state of each environment explicit and independently reviewable. Branch-per-environment can lead to complex merge conflicts when environments diverge significantly.
Immutable Promotion: The Right Way to Promote
A common mistake is to promote by changing a variable value in the environment directory. A better pattern is immutable artifact promotion: the Terraform module version or container image tag is pinned to an exact, tested artifact, and the promotion PR changes only that one version field. The reviewer knows exactly what is being promoted and can verify it was tested in staging.