← ResourcesDEVOPS Β· VERSIONING

Scalability and versioning of AWS infrastructure code

Git structure, progressive promotion and traceability for multi-environment IaC without drift.

STRALYA15 min readJuly 2026

Why version infrastructure code across multiple environments

Versioning infrastructure code is not a luxury: it is the foundation of reproducibility and traceability in an organization operating on AWS at scale. When several teams or several projects share the same cloud infrastructure, divergences accumulate quickly without structured versioning. The production environment eventually drifts away from staging, which itself has no connection to the source code. An emergency forces a manual change directly in the AWS console, and then the platform engineer has to guess what happened months later. A deployment fails, and no one knows whether it was a forgotten variable, an incompatible module version, or a local configuration. Traceability disappears, and incident post-mortems drag on. Versioning infrastructure code therefore means coding every AWS resource in a tool such as Terraform, Git, or CloudFormation, then ensuring that every change goes through a branch, a revision, a code review (the same process as business code), and a gradual promotion from dev to prod. This enforces a disciplined DevOps workflow: every version of the infrastructure code must be testable, reproducible on a fresh environment, and traceable back to the Git commit and the author of the change. Without this, even with Terraform, you are only a few months away from a chaotic AWS migration, impossible rollbacks, or constantly drifting costs. Multi-environment versioning is therefore not an optimization for later, it is a prerequisite as soon as the infrastructure grows beyond a dozen resources and involves more than one person.

Git structure and branches for infrastructure code

The first step in versioning infrastructure code is to treat Git as the single source of truth. This means a dedicated IaC repository (often called 'infrastructure' or 'terraform'), separate from application code, even when it is tempting to mix them. This repository follows a clear branching strategy: a main or master branch represents the production configuration, feature or hotfix branches for changes, and per-environment branches (or simply Terraform folders) for dev, staging, and prod. The most robust approach is to have one folder per environment in the same repository ('environments/dev', 'environments/staging', 'environments/prod'), each containing its own Terraform configuration with its variables (variables.tf) and its separate state files (state backend). This way, a change launched on dev can never accidentally touch prod. Each feature branch follows a strict process: an engineer creates a branch from main, codes the resources or modifications there, opens a pull request with a readable description ('Add RDS Aurora in prod for the new API'), and the review covers the code, the risks, and the estimated AWS costs. Once approved, merging the branch into main automatically triggers a CI/CD pipeline that validates the Terraform syntax, tests consistency (terraform plan), then requests manual approval before deploying to prod. This workflow guarantees that no one deploys directly in the AWS console, and that every change is written, reviewed, and traced in Git. Commit messages should be explicit ('Increase prod RDS vcpu to handle Q4 load') so that finding the author and the reason behind a resource two years later remains possible. Git tags serve to mark major versions ('v1.2.0', matching a stable release), which makes it easy to revert to an earlier version if a rollback is needed.

Validating and testing infrastructure code before deployment

Before deploying any resource to production, infrastructure code must pass a suite of automated validations. This step, often called 'infrastructure validation', is the pipeline's first safeguard. Basic validation covers the Terraform syntax (terraform validate), which runs quickly and rejects any malformed file. Then terraform fmt ensures that formatting is consistent across the entire codebase, which makes code reviews easier. But these checks are not enough. Syntactically correct Terraform code may not be logically sound: an AWS security group that allows inbound port 22 from 0.0.0.0 in production is a risk, even if Terraform validates it. This is why tools such as Checkov or TFLint scan the IaC code to detect security and compliance issues before deployment. A robust CI/CD pipeline runs these tools on every commit and blocks a pull request merge if critical issues are found. Then comes terraform plan, which builds a detailed execution plan: the list of resources that will be created, modified, or destroyed, plus an estimate of the additional AWS costs. This plan must be reviewed by a human, particularly in production where a mistake has financial and operational consequences. Some organizations go further and run minimal integration tests: after a terraform apply in a test environment, they verify that the created resources meet the expected criteria (for example, the RDS is reachable on the right port, the S3 bucket has the right permissions). These tests are written in Terratest (a Go framework) or in simple Python scripts, and they fail fast if the configuration was incorrect. Without these short feedback loops, an engineer only finds out when the cost alert arrives or when a user reports an outage.

Managing variables and secrets across environments

A Terraform infrastructure that deploys identically to dev and prod does not really exist: instance sizes, RDS replicas, ACLs, and cost tags all differ. The key is to parameterize this difference through Terraform variables and separate tfvars files per environment. Each environment folder (dev, staging, prod) contains a 'terraform.tfvars' file (or 'dev.tfvars', 'prod.tfvars') that specifies the values: 'instance_type = t3.micro' for dev and 't3.large' for prod, 'replica_count = 1' for dev and 3 for prod. These files are versioned in Git because they contain only non-sensitive parameters. Secrets (external API keys, RDS passwords, SSL certificates), on the other hand, must NEVER be in Git. These secrets are stored in AWS Secrets Manager or AWS Systems Manager Parameter Store, then injected into Terraform through data sources or through environment variables passed at the time of terraform apply. A CI/CD pipeline can retrieve these secrets from a secure vault (HashiCorp Vault, AWS Secrets Manager) at execution time and inject them only in memory, without ever writing them to disk. Some organizations adopt a strict HCL approach with Terraform workspaces ('terraform workspace select prod') to isolate state per environment, but this approach is less flexible because it does not easily allow differentiating the configuration (variables) between dev and prod. The separate-folders approach, with a shared .terraform-lock.hcl file for reproducibility of provider versions, is more recommended. This ensures that a secret never leaks into the commit logs, that a change to a production secret does not affect dev, and that each environment stays in control of its sensitive configuration.

Gradual promotion and approval between environments

Deploying infrastructure code to dev, validating, then to prod with a single click is appealing but risky. A mature DevOps approach sets explicit milestones: a feature branch, once approved, merges into a 'develop' branch that automatically deploys to dev. The infrastructure changes in dev, and it is tested for a few hours or days by the applications and internal users. Then the same code goes through an additional review, then is promoted to 'staging' for a more exhaustive battery of integration tests. Once validated in staging, an explicit approval request (in the CI/CD) is sent to the administrators or the DevOps lead, who review the terraform plan again, estimate the additional costs, and verify that no critical dependency is broken. Only after approval does the code merge into main and deploy to production. This dev -> staging -> prod progression introduces delays (a few hours, a few days depending on the culture), but it has measurable benefits: infrastructure bugs are caught in dev or staging rather than in prod where users are affected, additional costs are estimated and anticipated before the AWS bill arrives, and the team builds confidence by seeing the same changes tested without breakage. Some organizations, particularly scale-ups, add an intermediate step: a 'canary release' where the change deploys first to a fraction of prod (2-3% of traffic) through a blue/green configuration, then expands gradually if no error is detected. This reduces the risk of user-facing breakage while preserving velocity. Whatever the strategy, writing it explicitly in the CI/CD (a .gitlab-ci.yml file, GitHub Actions, or a Jenkins pipeline) and documenting it ensures that all engineers follow the same path and that an untracked change cannot happen by accident.

Tagging, documentation, and version traceability

Infrastructure that scales needs visibility: who created this resource, for which project, on what date, and with which version of the code? AWS tags and IaC code documentation answer this. Every AWS resource created by Terraform should be tagged with at least 'Environment' (dev/staging/prod), 'Project' (project name), 'ManagedBy' (Terraform, to avoid confusing it with manual resources), and 'CostCenter' (who pays?). These tags are defined once in the Terraform variables and applied to every resource (through a local or a default tags block), which ensures consistency. On the code side, every Terraform block should have an explicit comment above it, especially for non-obvious decisions. Why is this RDS Multi-AZ? What traffic volume justifies this instance size? The next person to read the code (or even you after 6 months) must understand the reason without guessing. Versions are marked through Git tags ('v1.5.0') and the main branch is always in production, which means any commit on main can be reviewed in Git to see what changed and when. A changelog or a VERSIONS.md file can document the key points of each release ('v1.5.0: Added RDS Aurora in prod, upgraded Terraform from 1.2 to 1.4'). Some organizations go deeper with an audit trail: every terraform apply generates a timestamped log with the user, the commit, the plan, and the result. These logs are stored in AWS CloudTrail or CloudWatch Logs for later inspection or for compliance. This level of traceability takes a few hours to set up, but it pays back tenfold in audits, incident response, and stakeholder confidence. Without traceability, it is the unanswered 'who broke prod?'; with it, it becomes a documented lesson.

Orchestrating versioning with a robust CI/CD

Versioning infrastructure code is an intention, but the CI/CD is what actually enforces it. A GitLab CI, GitHub Actions, or Jenkins pipeline must chain the steps: code checkout, Terraform validation, Checkov scan, terraform plan output, manual approval, terraform apply. For this to work without friction, a few prerequisites: the AWS credentials must be injected through secure environment variables (never hardcoded in the code), and the Terraform state must be stored in a remote backend (S3 with DynamoDB locks, or Terraform Cloud) so that several people can work in parallel without creating conflicts. The backend must have a backup strategy (S3 versioning, daily copies), because losing the Terraform state is a catastrophe. The .terraform-lock.hcl file must be versioned in Git to guarantee that all contributors use the same Terraform provider versions (avoiding a 'terraform init' that downloads the latest version and creates divergences). Finally, pipelines must be idempotent: a terraform apply run twice should give the same result as run once. If it does not, it is a sign that the IaC has an uncontrolled external dependency (for example, an AMI that changes, a randomly injected variable). All these elements combined make versioning infrastructure code not a matter of git and Terraform, but a DevOps culture where every change is written, reviewed, tested, and approved before affecting production. It is this coherent whole that turns a chaotic cloud infrastructure into a controlled, reproducible asset.

AWS TEARDOWN Β· FREE

Get the AWS Teardown: where your bill really goes.

The guide listing the 12 cost areas that leak the most at scale-ups, and how to plug them. Free, by email, no obligation.