Infrastructure as Code Discipline
IaC only pays off if you enforce it: plan before apply, review every change, and hunt down drift before it bites you at 3 a.m.
Writing a Terraform or CloudFormation template is the easy 20%. The senior skill is the discipline around it — the habits that keep your declared state and your real state honest over months of changes.
The non-negotiables
- Plan before apply.
terraform plan/ CloudFormation change sets show the diff before anything mutates. Post that plan on the pull request so a human sees what will change. - Remote, locked state. Terraform state lives in S3 with DynamoDB locking (or use S3 native locking) so two engineers can't apply at once and corrupt it.
- No console cowboy edits. Every change goes through the repo. A hotfix clicked into the console becomes drift — reality no longer matches code.
- Detect drift on a schedule.
terraform plan -detailed-exitcodeor CloudFormationdetect-stack-driftin a nightly job catches out-of-band changes early.
Why drift is dangerous
Drift is silent until the next deploy, when IaC tries to "correct" a manual change and reverts a fix — or worse, deletes a resource someone created by hand. Treat a drift alert like a failing test.
Example
# .github/workflows/terraform.yml — plan on PR, apply on merge
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions: { id-token: write, contents: read, pull-requests: write }
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-terraform-plan
aws-region: us-east-1
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -no-color -out=tfplan # diff posted to the PR
drift-check:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- run: terraform plan -detailed-exitcode || echo '::warning::drift detected'When to use it
- A team enforces a policy that no one can create AWS resources via the console in prod, only via Terraform, so the state file is always the source of truth.
- An engineer runs terraform plan in CI on every pull request and posts the diff as a comment so reviewers see infrastructure changes alongside code changes.
- Ops detects that a security group was manually widened in the console by running terraform plan, which shows an unexpected update, and reverts the drift.
More examples
Enforce IaC-only via SCP
Service Control Policy that denies EC2 and RDS creation unless the call comes through CloudFormation, enforcing IaC-only access.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": ["ec2:RunInstances", "rds:CreateDBInstance"],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:CalledVia": ["cloudformation.amazonaws.com"]
}
}
}]
}Detect Terraform drift in CI
Uses terraform plan's exit code 2 (changes present) to fail CI when actual infrastructure drifts from the Terraform state.
terraform plan -detailed-exitcode
EXIT=$?
if [ $EXIT -eq 2 ]; then
echo 'Drift detected: plan has changes'
exit 1
elif [ $EXIT -eq 0 ]; then
echo 'No drift — state matches infrastructure'
fiLock Terraform state with DynamoDB
Configures S3 backend with DynamoDB locking so concurrent applies are prevented and state is encrypted at rest.
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-state-locks"
encrypt = true
}
}
Discussion