Infrastructure Code Review Is More Consequential Than Application Code Review. Treat It That Way | PulseSoft
PulseSoft

Infrastructure Code Review Is More Consequential Than Application Code Review. Treat It That Way

Michael Emmanuel · August 11, 2026 · 12 min read

Introduction

A merged PR that introduces a bug in application code breaks a feature. A merged PR that introduces a bug in infrastructure code can delete a database, open a security group to the public internet, or terminate a production Auto Scaling Group. The blast radius is categorically different, and yet most engineering teams apply the same review process to both: a quick read of the diff, an LGTM, and a merge.

Infrastructure code review requires a different mental model than application code review. Application reviewers evaluate logic: does this function do what the author says it does, does it handle edge cases correctly, does it introduce a regression? Infrastructure reviewers need to evaluate configuration, state consequences, blast radius, and reversibility, four dimensions that don't appear in most code review checklists and that require specific knowledge of how AWS behaves under change to evaluate correctly.

This post covers what rigorous infrastructure code review actually looks like, the specific failure modes that thorough review prevents, and the structural practices (plan output in the PR, required reviewers for high-blast-radius changes, environment-specific approval gates), that make the process reliable rather than dependent on individual reviewer thoroughness.


Why the Standard Review Process Fails for Infrastructure

Application code review is mature. The tooling is good, the conventions are understood, and the feedback loop is fast: if a reviewer misses a bug, the test suite often catches it before the code reaches production. The worst case for a missed bug in application code is usually a P2 incident: something degraded but recoverable.

Infrastructure code has no equivalent safety net. Terraform has no test suite in the traditional sense. terraform validate checks syntax. terraform plan checks that the configuration is coherent and that the requested changes are feasible. Neither tells you whether the changes are correct, whether the security group rules reflect the intended access model, whether the RDS backup retention period meets your compliance requirements, whether the ECS task definition update will cause a rolling replacement that drops in-flight requests.

The first failure mode of standard review applied to infrastructure: reviewing the diff without reviewing the plan. A Terraform diff shows HCL changes. A Terraform plan shows what those HCL changes will do to actual AWS resources. These are different documents. A three-line HCL change can produce a plan with fifty resource modifications, because the changed value is a variable used in a for_each expression that fans out across resources. Reviewing the HCL diff tells you nothing about the plan output. Reviewing the plan output tells you what will actually happen.

The second failure mode: not distinguishing between reversible and irreversible changes. In AWS, some changes are reversible with low cost: changing a CloudWatch alarm threshold, updating an ECS desired count, modifying a route table. Others are expensive to reverse: scaling down an ECS cluster during a traffic spike, removing an IAM policy. And some are effectively irreversible without significant data risk: destroying an RDS instance without a backup, changing an S3 bucket name (which requires resource replacement), removing a DynamoDB table.

A reviewer who doesn't explicitly identify irreversible changes in a PR is leaving blast radius assessment entirely to the author: the person least likely to have fresh eyes on the risk.

The third failure mode: insufficient reviewer context. An application developer reviewing a Terraform PR who doesn't know that changing deletion_protection = false on an RDS instance removes the AWS-level protection against accidental deletion cannot evaluate whether that change is safe. Infrastructure code review requires infrastructure knowledge, and the reviewer pool needs to reflect that requirement.


AWS Deep Dive: What Infrastructure Review Actually Evaluates

Reading the Plan: The Document That Matters

The plan output is the primary artifact for infrastructure review, not the HCL diff. A PR review workflow that doesn't include the plan output in the PR comment is asking reviewers to evaluate intent without evidence of consequence.

The plan symbols that carry the most risk and require explicit review justification:

  • + create, low risk in isolation, but: what IAM permissions does this resource create, what network paths does it open, what does it cost?
  • - destroy, high risk; was this intentional? what data or configuration is lost?
  • -/+ destroy and re-create, highest risk; requires understanding why the replacement is triggered and what state or data is lost during the recreation window
  • ~ update in-place, generally low risk, but: is this update reversible? does it trigger a rolling restart of running tasks?
  • <= read: no change, data source refresh only

The -/+ symbol is the one most reviewers scan past without pausing. In Terraform, a resource replacement occurs when a change is made to an attribute that AWS cannot modify in place (it has to destroy the old resource and create a new one. For an ECS task definition, this is the normal lifecycle and carries no risk. For an RDS instance, a -/+ means the database is being deleted and recreated), all data is lost unless a snapshot is taken and restored. For an EC2 instance in an Auto Scaling Group, it means a brief capacity reduction during the replacement.

The non-obvious behavior that makes -/+ particularly dangerous: AWS resource replacement in Terraform is determined by provider logic, and that logic changes between provider versions. An attribute that was modifiable in place in provider version 4.x may trigger replacement in version 5.x. A provider version bump in a Terraform configuration can silently turn ~ updates into -/+ replacements for resources that were previously updated in place. Always review the plan output after a provider version update, even for changes that look purely cosmetic.

A GitHub Actions workflow that posts the plan output automatically to every PR comment removes the dependency on reviewers remembering to generate and read the plan themselves:

- name: Post Terraform Plan to PR
  uses: actions/github-script@v7
  if: github.event_name == 'pull_request'
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const { execSync } = require('child_process');
      const plan = execSync('terraform show -no-color plan.tfplan').toString();
      const truncated = plan.length > 65000
        ? plan.substring(0, 65000) + '\n\n... [truncated, see full plan in CI artifacts]'
        : plan;

      await github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: `## Terraform Plan\n\`\`\`\n${truncated}\n\`\`\``
      });

The 65,000 character truncation is necessary because GitHub comments have a 65,536 character limit. Plans that exceed this limit should be truncated with a link to the full plan in CI artifacts, never silently truncated in a way that hides the end of the plan, where destroys are often listed.

High-Blast-Radius Changes That Require Senior Review

Not every infrastructure PR carries equal risk. A tagging change to an existing resource carries near-zero risk. A change to a security group in the database tier of a production environment carries significant risk. Treating both with the same one-reviewer LGTM process is not a risk management strategy.

The resource and attribute categories that warrant a named senior reviewer requirement, not just any approver, and ideally a separate approval gate before production:

Security-relevant resources:

  • aws_security_group_rule with ingress from 0.0.0.0/0 or ::/0
  • aws_iam_role_policy, aws_iam_policy, aws_iam_role_policy_attachment in production accounts
  • aws_s3_bucket_public_access_block with any field set to false
  • aws_kms_key with key policy changes
  • aws_secretsmanager_secret_policy or aws_secretsmanager_resource_policy

High data-loss-risk resources:

  • aws_db_instance or aws_rds_cluster with -/+ replacement in the plan
  • aws_dynamodb_table with any change that forces replacement
  • aws_s3_bucket deletion or policy change
  • Any resource with lifecycle { prevent_destroy = false } where prevent_destroy was previously true

Cost-impact changes:

  • aws_instance type changes in Auto Scaling Groups across large fleets
  • New aws_nat_gateway or aws_vpc_endpoint resources
  • Storage type or IOPS changes on aws_ebs_volume or aws_db_instance

GitHub's CODEOWNERS file is the right mechanism for enforcing named reviewer requirements for specific paths:

# .github/CODEOWNERS

# All Terraform changes require platform team review
*.tf @yourorg/platform-team

# Production IAM changes require security team co-review
environments/production/iam.tf @yourorg/platform-team @yourorg/security-team

# Database infrastructure requires DBA and platform sign-off
modules/rds/ @yourorg/platform-team @yourorg/dba-team

The Review Checklist That Replaces Institutional Memory

Human reviewers are inconsistent under time pressure. A checklist enforced via PR template removes the dependency on individual reviewer memory for the mechanical checks that should happen on every infrastructure PR:

<!-- .github/pull_request_template.md for infrastructure PRs -->

## Infrastructure Change Checklist

**Author completes before requesting review:**

- [ ] Terraform plan output is included in this PR (automated via CI or pasted manually)
- [ ] All `-/+` (destroy and recreate) resources are explained in the PR description
- [ ] Breaking changes to module interfaces are documented with migration instructions
- [ ] New resources have required tags applied (Environment, Team, Service, ManagedBy)
- [ ] Any `lifecycle { prevent_destroy = false }` changes are explicitly justified

**Reviewer validates before approving:**

- [ ] Plan output reviewed (not just HCL diff), resource count and symbols match expected scope
- [ ] No unexpected `-/+` replacements for stateful resources (RDS, DynamoDB, EBS)
- [ ] No security group ingress rules opening to 0.0.0.0/0 without documented justification
- [ ] No IAM policy grants of `*` actions or `*` resources without documented justification
- [ ] Cost impact of new resources has been considered and is acceptable

**For production-environment PRs, additionally:**

- [ ] Change has been validated in a lower environment first
- [ ] Rollback procedure is documented (or rollback is not applicable, explain why)
- [ ] On-call engineer is aware this change is going out (if it could affect production behavior)

Enjoying this so far?

Join thousands of engineers receiving practical cloud, DevOps, Kubernetes, Infrastructure and AI engineering insights.

No spam. Unsubscribe anytime.

Tradeoffs & Decision Framework

The overhead of rigorous infrastructure code review is real. A PR that requires a plan output, a checklist, a named senior reviewer, and a separate production approval gate takes longer to merge than one with a single LGTM. That friction is the point: it exists to catch the changes that would have caused incidents.

The tradeoff is between merge velocity and change safety, and the right calibration depends on what's being changed and where.

Low-friction review appropriate for:

  • Development and sandbox environment changes where blast radius is isolated and no production data is at risk
  • Documentation, comment, and README updates in Terraform
  • Tag and metadata-only changes to existing resources
  • New non-production resources that don't interact with existing production infrastructure

High-friction review required for:

  • Any change to production security groups, IAM, or encryption configuration
  • Any plan that contains -/+ symbols on stateful resources
  • Module changes that affect multiple consumers across environments
  • Provider version bumps (because provider updates can silently change resource behavior)
  • Any change that removes or weakens a lifecycle { prevent_destroy } or deletion_protection setting

The failure mode of too much friction: Engineers route around the review process. PRs get merged on emergency branches that bypass required reviewers. Changes get made directly in the console to avoid the PR cycle. If the review process is too slow for operational reality, if an on-call engineer can't open a security group rule for emergency diagnosis because the PR review will take two hours, the process will be bypassed at exactly the moment when oversight matters most. Build a documented and fast break-glass exception process alongside the standard review gates, so the review process has a legitimate emergency bypass that's visible and auditable rather than an informal workaround.


Lessons From the Field

1. The plan output is the review artifact. The HCL diff is a supporting document. Reviewed a Terraform PR at a logistics client that changed a single variable in a VPC module. The HCL diff was two lines. The plan output was 340 lines, including a -/+ replacement for six route table associations that would have caused a two-minute routing outage during the apply. The reviewer who approved it before we added plan posting to the PR had reviewed the two-line diff and LGTM'd it. The reviewer who reviewed it after we added plan posting caught the route table replacements immediately.

2. A CODEOWNERS file that isn't enforced is decoration. Added CODEOWNERS for a fintech client's Terraform repository that required security team sign-off on IAM changes. Three months later, a PR modifying IAM policies in the production account merged without the security team reviewer, because GitHub branch protection had been set to require one reviewer, not specifically the CODEOWNERS reviewer. Set GitHub branch protection to explicitly require code owner review (require_code_owner_reviews: true in branch protection settings) or the CODEOWNERS file is just documentation.

3. Provider version bumps deserve the same review as functional changes. Upgraded the AWS Terraform provider from 4.x to 5.x for a SaaS client. The plan for a routine ECS service update showed seven -/+ replacements that had previously been ~ updates. The provider had changed which attributes triggered resource replacement in the 5.x release. Nobody anticipated this because the review process hadn't flagged the provider version bump as a high-risk change. After that incident, provider version bumps require a full plan review across all affected modules before merging.

4. Production approval gates only work if there's an environment parity requirement. Built a deployment pipeline for an e-commerce client where production Terraform applies required a separate approval after staging apply completed. The approval gate worked. The environment parity didn't, staging was on an older module version than production, so the staging plan wasn't representative of the production plan. The first time we caught a production-specific behavior that hadn't appeared in staging, it was in the production approval review rather than in staging testing. Environment parity between staging and production (same module versions, same provider versions, same variable values except for environment-specific ones) is a prerequisite for environment-based approval gates to mean anything.

5. The PR description is part of the review artifact, not a formality. Established a PR template for a platform team that required a description of intended changes, expected plan output summary, and rollback procedure. The first few PRs had thorough descriptions. After two months, descriptions had regressed to "Updates VPC module" and "Fixes tagging." Reinstated the checklist as a required PR template with blocking CI check on description completeness. The quality of the descriptions directly correlated with the quality of the reviews, reviewers who had context from the description asked better questions and caught more issues.


Final Thoughts

The tooling for infrastructure code review is improving. Atlantis and Terraform Cloud's VCS integration automate plan generation and posting. Policy-as-code tools like Checkov and OPA catch the mechanical security violations before human review. GitHub's CODEOWNERS, branch protection, and required status checks make review requirements enforceable rather than advisory.

What the tooling doesn't automate is judgment: the reviewer who recognizes that a security group change opens a path between the web tier and the database tier that wasn't there before, or the reviewer who notices that a variable change will cause rolling replacements for a service that doesn't handle SIGTERM gracefully. That judgment comes from infrastructure experience, and it requires reviewers who have enough context to evaluate the blast radius of the change they're approving.

The pattern that produces consistently rigorous infrastructure code review is treating it as a discipline with defined requirements (plan output, checklist, named reviewers for high-blast-radius changes), rather than a convention that relies on individual reviewer diligence. Conventions degrade under time pressure. Requirements, enforced by tooling, don't.

Embedding that discipline into the infrastructure delivery process is exactly the kind of platform work we do at PulseSoft. If your infrastructure review process has more LGTMs than it should, let's talk.


Key Takeaways

  • The Terraform plan output is the primary review artifact, not the HCL diff. A two-line HCL change can produce a 340-line plan including resource replacements that the diff doesn't reveal. Require plan output in every infrastructure PR comment, via CI automation or manual paste.
  • The -/+ symbol in Terraform plan output means destroy and recreate. For stateful resources like RDS instances, DynamoDB tables, and EBS volumes, this represents a data-loss event. Every -/+ on a stateful resource requires explicit justification in the PR description before review.
  • Provider version bumps can silently turn ~ updates into -/+ replacements. Attributes that were modifiable in place in a previous provider version may trigger resource replacement in a new one. Provider version upgrades require a full plan review across all affected modules, treat them with the same rigor as functional changes.
  • CODEOWNERS files require require_code_owner_reviews: true in GitHub branch protection to be enforced. Without it, the CODEOWNERS file is documentation that can be bypassed by merging with any single approver. The enforcement setting is separate from the file and must be explicitly configured.
  • Infrastructure PR checklists must be enforced by tooling, not convention. Checklists enforced only by social expectation degrade under delivery pressure. Use a PR template with blocking CI checks on description completeness to keep review quality consistent across time and reviewers.
  • Environment approval gates are only meaningful when environments have configuration parity. If staging uses different module versions or provider versions than production, the staging plan is not representative of the production plan. Environment parity (same versions, same structure, different values) is a prerequisite for staged deployment to mean anything.
  • Build a documented break-glass exception process alongside strict review gates. If the review process has no legitimate fast path for emergencies, engineers create informal workarounds that are invisible and unauditable. A break-glass process that's fast, documented, and generates an audit trail is better than a strict process that gets bypassed silently.

Get insights like this in your inbox

Join thousands of engineers receiving practical cloud, DevOps, Kubernetes, Infrastructure and AI engineering insights.

No spam. Unsubscribe anytime.

← Back to Blog