Filter concepts by levelShowing all levels.

AWS · Section 40

Infrastructure as Code

Level
intermediate
Read
40 min
Concepts
5

Infrastructure as code puts the definition of your infrastructure in version control and makes changing that definition the only way to change production. That buys reproducibility — staging is the same template with different parameters, and a Region migration is a parameter change — and reviewability, because a change is a diff read before it applies rather than an action taken in a console. CloudFormation is the AWS-native engine: a YAML or JSON template becomes a stack whose resources are created, updated, and deleted as one unit, with parameters supplying deploy-time values, outputs crossing stack boundaries, and a change set showing proposed changes including the replacements that quietly destroy data. The CDK generates those same templates from a real programming language, composing L1/L2/L3 constructs into stacks and apps — which means the synthesized template, not the code diff, is what you review. Terraform solves the same problem cloud-agnostically, and its one structural difference is state: a record of what it created that you must store in a locked, encrypted remote backend, because concurrent applies corrupt it and it can contain secrets. The practical advice is to pick one and get deep enough to handle failures, know the others well enough to read a repository, and never let two tools own the same resource.

This section

What is true here

  1. IaC buys reproducibility and reviewability — and only if the code is the sole path to production.
  2. Template → stack; a change set shows proposed changes, including resource replacements, before they happen.
  3. CDK generates CloudFormation, so cdk diff and the change set are the real review artifacts.
  4. Terraform's distinguishing feature is state, which you own and must lock, encrypt, and keep out of version control.
  5. One tool deep beats three shallow, and one owner per resource is non-negotiable.

What you will be able to do

  • Explain what reproducible and reviewable infrastructure actually buys, and what drift costs
  • Write a CloudFormation template using parameters, conditions, intrinsic functions, and outputs
  • Create and read a change set, and spot a change that will replace a stateful resource
  • Describe CDK's construct layers and synthesis, and Terraform's state, plan, and apply model
  • Choose a primary IaC tool for a given team and estate, and avoid split resource ownership
From the principle to a tool you can operate under pressure
implementedbygenerated byalsoimplemented byweighed inweighed in

Reproducible and reviewable

CloudFormation — the engine

CDK — a generator over it

Terraform — state you own

One deep, the rest as vocabulary

  • Reproducible and reviewable
    • leads to CloudFormation — the engine (implemented by)
    • leads to Terraform — state you own (also implemented by)
  • CloudFormation — the engine
    • leads to CDK — a generator over it (generated by)
  • CDK — a generator over it
    • leads to One deep, the rest as vocabulary (weighed in)
  • Terraform — state you own
    • leads to One deep, the rest as vocabulary (weighed in)
  • One deep, the rest as vocabulary

Infrastructure as Code

The principle, CloudFormation as the AWS-native engine, CDK and Terraform on top of and beside it, and how to choose.

Reproducible and Reviewable Infrastructure

coreintermediate

Infrastructure as code means the definition of your infrastructure lives in version control, and changes reach production by changing that definition. Two properties follow. Reproducible: you can rebuild the environment from the file, in another Region or another account. Reviewable: a change is a diff someone can read before it happens, not an action someone took in a console.

Think of it as

Console clicks are verbal instructions; a template is a written contract. Both get the work done today. Only one of them can be re-read next year, applied twice with the same result, or disagreed with before it takes effect.

What we're doing: See what "reproducible" buys the first time you need a second environment.

second-environment.txttext
Console-built production: VPC, subnets, ALB, ECS service, RDS, security
groups, IAM roles. Nobody wrote any of it down.

Request: "give us an identical staging environment".
Reality: weeks of archaeology, and staging still differs in ways nobody
finds until a deploy behaves differently there.

Same request, template-built production:
  deploy the same template with Environment=staging and a smaller
  instance class. Hours, and the differences are the parameters.
1
Nothing here is wrong on day one. The cost is entirely deferred to the first time someone needs a second copy or a rebuild.
5
The dangerous part is not the effort — it is that the environments differ in ways nobody can enumerate, so testing in staging stops meaning anything.

Why this works: Reproducibility is not an aesthetic preference. It is what makes staging a valid test of production, what makes disaster recovery a rehearsal instead of an improvisation, and what makes a Region migration a parameter change instead of a project.

Making an emergency change in the console and not backporting it

Wrong

text
# 02:00, incident: bump the ASG max size in the console. Fixed. Done.

Better

text
# 02:00: make the change (that is fine). 09:00: put the same change in
# the template and deploy it, so the definition matches reality again.

What you see: The next stack update silently reverts the emergency fix, because the template still says the old value and CloudFormation applies what the template says.

Why: An IaC tool reconciles reality toward the definition. A hand-made change that is not reflected in the definition is not preserved — it is scheduled for deletion at the next deployment, at a moment nobody will connect to the change.

Two ways to change production

Console change

  • +Reviewed after the fact, if at all
  • +Reproducible only from memory
  • +Rollback means remembering the old value
  • +Environments drift apart silently

Infrastructure as code

  • Reviewed as a diff before it applies
  • Rebuildable in another account or Region
  • Rollback is reverting a commit
  • Drift is detectable, because there is a definition to compare against
  • Console change
    • Reviewed after the fact, if at all
    • Reproducible only from memory
    • Rollback means remembering the old value
    • Environments drift apart silently
  • Infrastructure as code
    • Reviewed as a diff before it applies
    • Rebuildable in another account or Region
    • Rollback is reverting a commit
    • Drift is detectable, because there is a definition to compare against

Remember: Infrastructure as code buys two things: reproducibility (rebuild it elsewhere from the file) and reviewability (read the diff before it applies). Both depend on the code being the only path to production — otherwise the definition drifts into fiction.

See also: cloudformation fundamentals · choosing one tool deeply · instance replacement over hand tuning

CloudFormation Fundamentals

coreintermediate

A CloudFormation template is a YAML or JSON file describing the AWS resources you want. A stack is what you get when you deploy one: the related resources managed as a single unit, created, updated, and deleted together. To change a running stack you submit a modified template, and a change set shows you what CloudFormation intends to do before it does it.

Think of it as

The template is the blueprint and the stack is the building. You do not renovate the building directly — you edit the blueprint and let CloudFormation work out which walls that implies moving. The change set is the builder telling you, before starting, that this particular edit means demolishing the kitchen.

What we're doing: Catch a change that would silently destroy a production database.

replacement-in-a-change-set.txttext
Template edit: rename the RDS logical id from "Db" to "PrimaryDb".

Change set output:
  Action: Remove   LogicalResourceId: Db
  Action: Add      LogicalResourceId: PrimaryDb

Reading it: CloudFormation will create a new database and delete the old
one. The data in the old database is gone unless it was backed up.

Without the change set, "just a rename" is applied and the data is gone.
3
The logical id is part of the resource's identity to CloudFormation. Renaming it is not a rename — it is a delete plus a create.
6
AWS calls this out explicitly in its own documentation using the RDS example, because it is the change that most commonly surprises people.

Why this works: Some property changes are updated in place, some force a replacement, and the difference is not obvious from the template. The change set is the only place that distinction is stated before the change happens, which is why "always create a change set for production" is worth the extra step.

Deleting a stack to "clean up", forgetting it owns stateful resources

Wrong

text
aws cloudformation delete-stack --stack-name legacy-api

Better

text
# Set DeletionPolicy: Retain (and UpdateReplacePolicy: Retain) on
# databases, buckets, and log groups before the stack ever goes to prod

What you see: The stack deletes cleanly, and takes the RDS instance and the S3 bucket with it — resources whose lifetimes were never meant to be tied to that stack.

Why: A stack owns its resources: deleting the stack deletes them by default. `DeletionPolicy: Retain` is what expresses "this resource outlives the stack", and it has to be set before the deletion, not discovered after it.

Updating a stack safely

Edit the template

The change is a reviewable diff in version control

Create a change set

CloudFormation compares modified against original

Read the Replacement column

"True" on a database means data loss unless handled

Execute, or discard and try again

You can create as many change sets as you need

On failure, CloudFormation rolls back

It restores the last known working state

  1. Edit the template — The change is a reviewable diff in version control
  2. Create a change set — CloudFormation compares modified against original
  3. Read the Replacement column — "True" on a database means data loss unless handled
  4. Execute, or discard and try again — You can create as many change sets as you need
  5. On failure, CloudFormation rolls back — It restores the last known working state

Template sections and what each is for

Template sections and what each is for
SectionPurposeTypical content
ParametersValues supplied at deploy timeEnvironment name, instance class, VPC id
MappingsStatic lookup tables resolved at deploy timeAMI id per Region, CIDR per environment
ConditionsWhether a resource or property applies`IsProd: !Equals [!Ref Env, prod]`
ResourcesThe only required section`AWS::EC2::Instance`, `AWS::RDS::DBInstance`
OutputsValues to show, or export for other stacksALB DNS name, database endpoint

Together

yaml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  Env:
    Type: String
    AllowedValues: [staging, prod]
Conditions:
  IsProd: !Equals [!Ref Env, prod]
Resources:
  ApiLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ecs/api-${Env}'
      RetentionInDays: !If [IsProd, 400, 14]
Outputs:
  LogGroupName:
    Value: !Ref ApiLogs

Remember: Template (YAML/JSON blueprint) → stack (resources managed as one unit). Parameters vary per deploy, Mappings are static lookups, Outputs cross stack boundaries. Always create a change set for production, and read the Replacement column. Split stacks by how often the resources change.

See also: cdk concepts · terraform concepts · reproducible and reviewable infrastructure

AWS CDK Concepts

standardintermediate

The AWS CDK lets you define infrastructure in a general-purpose programming language instead of YAML. You compose constructs — objects representing one or more AWS resources — into stacks, and stacks into an app. Running `cdk synth` turns all of that into a CloudFormation template, which is what actually gets deployed. CDK is a generator; CloudFormation is still the engine.

Think of it as

CDK is a compiler for CloudFormation. Your TypeScript or Python is the source, the synthesized template is the object code, and the stack is the running program. When something behaves unexpectedly, the synthesized template is where you look — the same way you would read generated SQL rather than guess at the ORM.

javascript
// An L2 construct: sensible defaults, still one CloudFormation stack underneath
const queue = new sqs.Queue(this, 'OrderJobs', {
  visibilityTimeout: Duration.seconds(300),
  encryption: sqs.QueueEncryption.KMS_MANAGED,
});

// Environment: account + Region, needed for account-specific lookups
new ApiStack(app, 'ApiProd', {
  env: { account: '111122223333', region: 'eu-west-1' },
});

Treating the CDK code as the reviewable artifact and never reading the synthesized template

Wrong

text
# Review the TypeScript diff, deploy, hope the generated template matches

Better

text
# cdk diff against the deployed stack, and read the CloudFormation
# change set for production — that is what will actually be applied

What you see: A small code change — swapping an L2 construct, upgrading the CDK version — produces a template that replaces a stateful resource, and nobody sees it because the code diff looked harmless.

Why: The mapping from construct to resource is not one-to-one and can change between CDK versions. The template is what CloudFormation executes, so the template diff is what carries the replacement information a code diff cannot show.

The three construct layers

The three construct layers
LayerWhat it isReach for it when
L1 (Cfn*)A direct, generated mapping of a CloudFormation resourceA property is not yet exposed by a higher layer
L2A curated wrapper with defaults, helper methods, and IAM grantsAlmost always — this is the intended level
L3 (patterns)Several resources composed into one architectural shapeThe pattern matches what you actually want, exactly

Together

javascript
// L2 gives you grants instead of hand-written policy documents
const bucket = new s3.Bucket(this, 'Uploads');
bucket.grantRead(taskRole);   // synthesizes the IAM policy for you

Remember: CDK generates CloudFormation. Constructs (L1 raw, L2 curated, L3 patterns) compose into stacks, stacks into an app, and `cdk synth` produces the template that is actually deployed — so `cdk diff` and the change set, not the code diff, are what you review before production.

See also: cloudformation fundamentals · choosing one tool deeply

Terraform Concepts

standardintermediate

Terraform is a cloud-agnostic IaC tool. A provider teaches it how to talk to a platform (the AWS provider, for example); resources describe what you want; modules package a set of resources for reuse. The piece with no CloudFormation equivalent is state: a file Terraform keeps that maps your configuration to the real objects it created, and which you have to store and protect deliberately.

Think of it as

CloudFormation keeps its record of what it created on the AWS side, so there is nothing for you to store. Terraform keeps that record in a file you own. Everything distinctive about operating Terraform — remote backends, locking, who may read the file — follows from that single difference.

text
# The everyday loop
terraform init      # download providers, configure the backend
terraform plan      # what would change, and why
terraform apply     # make it so

Keeping state in the repository, or on one engineer's laptop

Wrong

text
# terraform.tfstate committed to git alongside the configuration

Better

text
# A remote backend with locking and encryption, and .gitignore for
# *.tfstate*

What you see: Two people apply at once and one set of changes is silently lost, or a resource is created twice — and the secrets inside the state file are now in the repository history for everyone with read access.

Why: HashiCorp's own guidance is to avoid storage that does not support state locking and secure access control, for exactly these two reasons: concurrent applies corrupt the record of what exists, and the file itself can contain secrets.

Terraform and CloudFormation, term by term

Terraform and CloudFormation, term by term
TerraformCloudFormationNote
Configuration (`.tf` files)TemplateBoth declarative
`terraform plan`Change setBoth show intent before applying
`terraform apply`Create/update stackTerraform has no automatic rollback on partial failure
ModuleNested stackModules are the more ergonomic of the two
State file (yours to store)Managed by CloudFormationThe main operational difference
ProviderBuilt in to the serviceOne Terraform configuration can span AWS plus other platforms

Together

text
# Remote state on AWS: an S3 backend with locking
terraform {
  backend "s3" {
    bucket       = "acme-tfstate"
    key          = "prod/api/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true
  }
}

Remember: Providers + resources + modules describe the infrastructure; `plan` then `apply` changes it. The distinctive part is state — Terraform's record of what it created, which you must store in a locked, encrypted remote backend, because concurrent applies corrupt it and it can contain secrets.

See also: cloudformation fundamentals · choosing one tool deeply

Choosing One IaC Tool, Deeply

standardintermediate

CloudFormation, CDK, and Terraform all solve the same problem, and the difference between knowing one of them well and knowing three of them shallowly is large. Depth is what lets you read a failed deployment, understand why a resource is being replaced, and recover a broken stack. Breadth is worth having as vocabulary, so you can read someone else's repository — not as a second toolchain in your own.

Think of it as

These are three languages for the same conversation. Being fluent in one and able to follow the other two is far more useful than speaking all three like a tourist — because the moments that matter are the ones where a deploy has half-failed at 3am, and that is not the time to be reading unfamiliar error output.

text
# One owner per resource — the rule that prevents the worst outcome
network/     -> Terraform
application/ -> Terraform      # not CDK; do not split ownership per resource
data/        -> Terraform

Managing the same resources with two tools

Wrong

text
# The VPC is in Terraform; the ECS service in CDK also declares subnets
# and security group rules on it

Better

text
# One tool owns each resource. Cross-tool references are read-only
# lookups (data sources / imports), never writes.

What you see: Every apply of one tool reverts a change made by the other, and the environment oscillates between two configurations depending on which pipeline ran last.

Why: Both tools reconcile reality toward their own definition. Two definitions of the same resource means each apply is a correction of the other, which is not a bug in either tool — it is two owners with equal authority and no shared source of truth.

Choosing a primary tool

Choosing a primary tool
SituationPrimary toolReason
All-AWS, small team, no existing IaCCloudFormationNothing extra to install, run, or secure
All-AWS, application developers own the infrastructureCDKReal language, typed reusable constructs, testable
AWS plus other platforms, or an existing module libraryTerraformOne configuration language across providers
A team already fluent in one of themThat oneFluency in failure handling outweighs tool features

Together

text
# What "deep" actually means, in any of the three
#  - recover a stack stuck in UPDATE_ROLLBACK_FAILED
#  - import an existing resource into management
#  - move a resource between stacks without recreating it
#  - explain why a change forces replacement

Remember: Pick one tool and get deep enough to handle failures — stuck updates, imports, moves, replacements. Learn the other two well enough to read a repository. Never let two tools own the same resource, or every apply becomes a correction of the last one.

See also: cloudformation fundamentals · cdk concepts · terraform concepts

Advertisement