Skip to main content

Infrastructure as code

Infrastructure as code (IaC) means the AWS Organization, its accounts, networks, security services, identity and pipelines are described in files, reviewed as pull requests, and created by a pipeline that runs the same code every time. In the BuiltForProd platform nothing is clicked together in a console: if it is not in a repository, it does not exist, and if it appears in an account without a commit, it is drift.

Why it matters in production

A production environment built by hand cannot be rebuilt, reviewed or reasoned about. The questions that matter after an incident (what changed, who changed it, can we recreate it in another region) have no answer when the infrastructure lives in a console history. IaC gives every change an author, a review, a plan that shows the effect before it happens, and a history that can be reverted.

It also makes environments alike. Development, staging and production are generated from the same definitions with different values, so "it works in staging" means something. And it makes the platform transferable: a customer receives repositories, not a running environment nobody can reproduce.

How the platform applies it

BuiltForProd ships seven repositories. The landing zone (acme-aws-platform-baseline) is the hub every other repository depends on. The blueprint infrastructure repositories (acme-aws-blueprint-webapp-infra, acme-aws-blueprint-etl-infra) build workloads on top of it. The code repositories, the GitOps repository and the secrets repository complete the set. All infrastructure is written in OpenTofu modules orchestrated by Terragrunt.

The mechanics that make this hold:

PracticeWhere it is enforced
One definition per unit, generated per account or stageunits/, stacks/, environments/ (units and stacks)
No hard-coded CIDRs; every address comes from the VPC map through IPAMscripts/check-no-hardcoded-cidrs.py, scripts/check-vpc-maps.py
No account IDs, secrets or keys in codeOrganizations outputs, SOPS-encrypted secrets, write-only arguments
Every dependency has complete mock outputs, so a new environment plans before anything existsscripts/check-mock-outputs.py
Every unit supplies its module's required inputsscripts/check-required-inputs.py
Versions pinned in one placeroot.hcl (OpenTofu, Terragrunt, AWS provider), scripts/check-module-versions.py
Changes reach AWS only through the pipelineplan.yml on pull requests, apply.yml on merge to main, serialized applies

State is partitioned per unit and stored in one encrypted, versioned S3 bucket in the management account, acme-use1-root-tfstate, with native locking. A bad write is recoverable from a previous version, and the per-unit split keeps the blast radius of any run small.

Deployment-specific values are tagged, not buried

The repositories are templates: they are forked and customized per customer. Everything that must be set for a deployment carries a # TODO: comment, and everything that may be changed carries a # @optional: comment with its consequence and, where it costs money, its price.

Finding every choice and every required value
git grep "TODO: " # things you MUST set for your deployment
git grep "@optional: " # things you MAY change; the platform works without them

A TODO: never marks something optional, and something optional is never a TODO:. The deployment guides tell operators to search for TODO: to find what they must set.

Worked example: adding a region

Adding a region to the landing zone is a folder change, not a code change. The address plan already reserves space for it in eks_vpc_map.yaml. An engineer adds environments/<ou>/<account>/<region>/ folders with a region.hcl (which declares only the region slug, because aws_region is derived from the folder name) and a terragrunt.stack.hcl listing the region's units. The ipam unit discovers the new region from the tree and fails the plan if the folder and the map disagree. The region-restriction service control policy picks the region up from the same discovery. No unit definition is edited.

Common mistakes

  • Treating the console as a shortcut. A console change is not in the code, so the next apply reverts it or the drift job reports it. Make the change in a pull request.
  • Applying from a laptop. Changes reach AWS through pull request, plan, merge and the apply workflow. A local apply bypasses review, the environment gate and the serialized queue.
  • Typing an IP range or an account ID. The guards reject CIDR literals; account IDs come from the Organizations unit's outputs.
  • Editing generated files. Anything under .terragrunt-stack/ is regenerated; change the definition or the stack file.
  • Changing a default without updating its tag or its price comment. The tags are how the next reader finds what was decided.