SMS Blog

Terraform Infrastructure as Code: How to Build Auditable AWS Infrastructure

An engineer spins up a new EC2 instance to fix an urgent issue. It works, so nobody documents the change. Six months later, an auditor asks why that instance has different security group rules than every other server in the fleet. Nobody remembers making the change, and nobody can prove who approved it.

This is how most AWS environments get built: one console click at a time, by different people, on different days, for different reasons. It works until someone needs to explain exactly what’s running and why.

For regulated industries, that gap between “it works” and “we can prove how it works” isn’t a technical inconvenience. It’s the difference between passing an audit and failing one.

So what does it actually take to build AWS infrastructure that holds up under scrutiny?

If you’re new to DevOps or IaC, start with the basics: How to Use Infrastructure as Code with Terraform on AWS

Why Manual AWS Management Breaks Down at Scale

A handful of manually configured servers is manageable. A production environment with dozens of services, multiple accounts, and several environments is not. At least, not without a system behind it.

The Core Problems

  • No single source of truth: What’s deployed in AWS often only exists in the console itself. If the console is the only record, there’s no way to compare “what should be there” against “what’s actually there.”
  • Invisible changes: A change made by hand doesn’t announce itself. Nobody finds out a setting was altered until it causes an outage or fails a security scan.
  • Nothing to rebuild from: Standing up a new environment, whether for disaster recovery, a new region, or a new client, means reconstructing everything from memory or scattered documentation.

What This Looks Like in Regulated Industries

Auditors don’t ask teams to describe their process from memory. They ask for evidence.

  • “We’re confident it’s configured correctly” isn’t an answer an auditor can accept.
  • Every change needs to be traceable to a person, a reason, and an approval.
  • Without a system that enforces this automatically, it falls on individual discipline, which doesn’t scale and doesn’t survive staff turnover.

Manual infrastructure management isn’t just slower. It leaves teams unable to answer the one question that matters most in a compliance review: how do you know?

What is Terraform?

Terraform describes Infrastructure as Code (IaC). Instead of clicking through the AWS console to create a VPC or a load balancer, an engineer writes a configuration file that states what should exist. Terraform IaC handles the rest.

The Mechanics in Plain Terms

  • Configuration files define the desired state: These files describe what the infrastructure should look like: which resources exist, how they’re connected, and what settings they use.
  • Terraform checks that state against reality: Before making any change, Terraform compares what’s described in code to what actually exists in AWS.
  • A plan shows the difference before anything happens: Terraform generates a plan that lists exactly what will be created, changed, or destroyed, so nothing is applied blind.

How This Differs From AWS-Native Tools

AWS offers its own Infrastructure-as-Code (IaC) tool, CloudFormation. Terraform’s appeal for many teams comes down to its ability to manage resources across multiple cloud providers and services with one consistent workflow, rather than being tied to a single ecosystem.

The bigger point isn’t which tool wins. It’s that once infrastructure lives in code, every environment can be built the same way, every time, with a record of exactly what was done.

Module Structure That Scales Without Becoming Unmanageable

Writing Terraform code is the easy part. Structuring it so a team can maintain it a year later is where most projects succeed or fail.

Why Flat Configurations Fall Apart

A single, unstructured set of Terraform files might work for one environment. The moment a second environment is needed, teams often just copy and paste the original files and change a few values. This creates duplicated code that has to be updated in multiple places every time something changes, and it’s easy for environments to gradually drift apart.

A Structure That Holds Up

  • Root modules per environment: Development, staging, and production each get their own root module, so changes to one don’t accidentally affect another.
  • Shared modules for repeatable components: Common building blocks such as VPCs, IAM roles, and security groups live in shared modules that every environment references.
  • Environment-specific variables layered on top: Instead of duplicating logic, each environment supplies its own values (instance sizes, account IDs, tags) to the same underlying modules.

Naming and Folder Conventions Matter

  • Consistent naming across modules cuts onboarding time for new engineers significantly, since they can predict where things live.
  • Clear boundaries between modules make code review faster, because reviewers can reason about one component without needing the full picture of every other resource.
  • A predictable structure reduces the chance that a small change in one part of the infrastructure has unexpected effects somewhere else.

Good module structure isn’t about following a rigid template. It’s about making the codebase legible to someone who didn’t write it.

Considering a move to AWS? Make sure you’re following the right plan: Cloud Migration Checklist: 15 Steps for Moving to AWS

State Management (Where Most Teams Get Into Trouble)

Terraform keeps a file, called state, that records what it believes exists in AWS. Every time Terraform checks for changes or makes them, it relies on that file being accurate. When it isn’t, things go wrong quickly.

Best Practices for Remote State

  • Store state in S3 with versioning enabled: Keeping state on a local machine means it can be lost, overwritten, or simply unavailable to the rest of the team. S3 with versioning keeps a history and a shared source of truth.
  • Use DynamoDB for state locking: Locking prevents two people from running Terraform against the same environment at the same time, which avoids conflicting changes being applied simultaneously.
  • Separate state files per environment: Keeping development, staging, and production state files apart limits the damage if something goes wrong in one environment.

Common Mistakes and Their Consequences

  • Manual changes outside Terraform: Editing a resource directly in the AWS console causes the real infrastructure to drift from what Terraform’s state file believes exists, and that mismatch tends to surface at the worst possible time.
  • Shared state files across teams: When multiple teams write to the same state file without clear boundaries, one team’s change can silently overwrite another’s.
  • Skipping locking: Without it, two people can apply changes at once, and the result is often a corrupted state file that takes real effort to repair.

State management doesn’t get much attention until it fails. By then, the team is usually trying to reconstruct what’s running in production, which is the exact problem Terraform was supposed to solve in the first place.

The Governance Layer That Makes Terraform Auditable

Code alone doesn’t make infrastructure auditable. What matters is the layer of process wrapped around that code, the part that turns “we wrote it in Terraform” into “we can prove exactly what happened and why.”

Version Control as the Audit Trail

  • Every change to infrastructure code lives in Git, with a commit history showing who made it and when.
  • Pull requests create a natural checkpoint. Nothing reaches production without someone else reviewing it first.
  • This history exists automatically, as a byproduct of how the team works, rather than something someone has to remember to document separately.

Policy as Code

  • Tools such as Sentinel or Open Policy Agent let teams write rules directly into the deployment process: no public S3 buckets, mandatory encryption, approved instance types only.
  • These policies run automatically before a change is applied, catching violations before they happen rather than flagging them in a review weeks later.
  • Rules enforced by a policy engine don’t depend on someone remembering to check manually.

Separating Plan from Apply

  • Requiring a reviewed plan before anything is applied creates a clear decision point: here’s what will change, and here’s who approved it.
  • A CI/CD pipeline can enforce that separation automatically, so it doesn’t rely on individual habits holding up under deadline pressure.

During an Actual Audit

Instead of describing a process from memory, a team can point to a specific commit, the plan that was generated, and the approval attached to it. That’s the difference between telling an auditor how things work and showing them.

Thinking about shifting your on-premises database? Make sure you’ve got the right tools for the job: A Guide to AWS Database Migration Service

Common Mistakes When Applying Terraform

Terraform IaC solves real problems, but adopting it well takes more than installing the tool and writing a few configuration files.

  • Treating it as a one-time migration: Moving existing infrastructure into Terraform is only the start. The discipline has to hold going forward, or the codebase drifts from reality just as manual infrastructure did.
  • Skipping module design early: It’s tempting to get something working quickly and worry about structure later. Later usually means after the codebase has already become difficult to untangle.
  • Underestimating training needs: Learning Terraform syntax is straightforward. Learning to read a plan carefully, understand its implications, and know when to push back takes longer and matters more.

Signs a Team Isn’t Ready Yet

  • No CI/CD pipeline to enforce plan review before changes go live
  • No established code review habits within the engineering team
  • No clear ownership of who’s responsible for state files and module changes

None of these are reasons to avoid Terraform. They’re reasons to build the surrounding process at the same time as the code.

Proof Over Promises: Why Experience with AWS Governance Matters

Getting Terraform working is one thing. Getting it working in a way that satisfies auditors, survives staff turnover, and holds up across dozens of AWS accounts is another.

SMS has spent years applying federal-grade security and compliance standards to cloud environments, including the kind of rigorous, evidence-based governance that regulated industries can’t compromise on.

That background shapes how we approach every Terraform implementation: not just writing code that works, but building the module structure, state management, and policy controls that make an environment genuinely auditable from day one.

If your team is weighing how to bring Terraform into a regulated AWS environment, or trying to fix one that’s already grown unwieldy, we’ve done this work before and know where the risks are.

Talk to an AWS Consultant

Picture of Andrew Stanley

Andrew Stanley

Andrew Stanley, SMS' Chief Technology Officer, joined in 2002 as a junior network engineer, supporting Department of Defense IT infrastructures and leading programs for the Executive Office of the President and DARPA. Promoted to Director of Engineering in 2021, he drove talent development and innovation across the company. A private pilot at 16 and former U.S. Army Information Systems Analyst, Andrew earned an IT degree from George Mason University through the Army's Green to Gold program. View Andrew's LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *