AWS re/Start Lab · JAWS · Infrastructure as Code

Automating Deployments with AWS CloudFormation

Deploy, modify, and destroy AWS infrastructure as a single declarative unit using CloudFormation stacks. The lab walks a YAML template from a bare VPC + Security Group baseline to a fully wired stack that includes an S3 bucket and an EC2 instance, then tears everything down with a single delete operation.

Lab Summary

Built and torn down a CloudFormation stack named Lab across four tasks. Each task isolates one IaC concept: initial deploy, incremental edit with a minimal resource, incremental edit with cross-references, and full teardown.

Task 1 — Deploy

Uploaded task1.yaml and created the Lab stack with 8 networking resources: VPC, Subnet, IGW, Route Table, Route, Route Table Association, IGW Attachment, and a Security Group.

Tasks 2 & 3 — Configure

Edited the template to add an S3 bucket (2 lines) and an EC2 instance with 5 properties wired to the VPC resources via !Ref. Updated the stack via change sets.

Task 4 — Terminate

Deleted the stack. CloudFormation removed every resource it managed in reverse dependency order, no manual cleanup required.

Anatomy of a CloudFormation Template

CloudFormation templates are YAML (or JSON) documents with three primary sections. Indentation is significant: two spaces per level, hyphens for list items.

Section Purpose What this lab used it for
Parameters Inputs prompted at deploy/update time. Can have defaults and types. Two CIDR ranges for VPC and Subnet. In Task 3, the SSM-backed AmazonLinuxAMIID parameter.
Resources The infrastructure to create. Each resource has a logical ID, a type, and optional properties. VPC + 6 networking resources + Security Group baseline. Later: S3 bucket and EC2 instance.
Outputs Selective information exported from the stack (e.g. ARNs, IDs) for humans or other stacks to reference. The default Security Group ID of the created VPC.
Cross-references with !Ref: CloudFormation does not require explicit ordering of resources. When PublicRouteTable declares VpcId: !Ref LabVPC, CloudFormation infers the dependency and creates the VPC first. This implicit dependency graph is one of the highest-value features of IaC.

Step-by-Step Walkthrough

Four tasks, executed against the same stack named Lab in us-west-2. Evidence captured at the points where the lifecycle status visibly changes.

01

Deploy the base stack (VPC + Security Group)

  • Downloaded task1.yaml and inspected the three sections: two CIDR parameters (VPC and Subnet) under Parameters, eight resources under Resources, and one Security Group ID under Outputs.
  • In the CloudFormation console, chose Create stack → With new resources (standard), selected Upload a template file, and uploaded task1.yaml.
  • Set the stack name to Lab. Kept the default CIDR values from the template parameters. Acknowledged the IAM capability prompt on the review page (some resources use custom names).
  • The stack entered CREATE_IN_PROGRESS. CloudFormation determined the optimal creation order automatically — the VPC before any resource that depends on its ID, the IGW before its VPCGatewayAttachment, and so on.
  • After roughly one minute, the status moved to CREATE_COMPLETE with all 8 resources in the green state.
What the 8 resources actually are: LabVPC (VPC), PublicSubnet (Subnet), IGW (Internet Gateway), VPCtoIGWConnection (VPCGatewayAttachment), PublicRouteTable, PublicRoute (the 0.0.0.0/0 route via the IGW), PublicRouteTableAssociation, and AppSecurityGroup. This is the minimum viable public network in AWS.
02

Add an S3 bucket via stack update

  • Edited task1.yaml locally and appended the minimum-viable S3 resource under Resources. Two lines total — logical ID and type:
Resources:
  # ... existing VPC / Subnet / SG resources above ...

  S3Bucket:
    Type: AWS::S3::Bucket
  • In the console, selected the Lab stack, clicked Update, chose Replace current template, and uploaded the edited file.
  • CloudFormation calculated a change set and showed exactly one change: Add a new resource of type AWS::S3::Bucket. Every other resource was marked unchanged, confirming no redeployment of the networking layer.
  • Executed the change set. Stack moved UPDATE_IN_PROGRESSUPDATE_COMPLETE in under a minute. The bucket appeared in the Resources tab with a CloudFormation-generated physical name.
Why this matters: the change set is a dry run. Before clicking Execute, the operator sees the exact diff against the live stack. In a production environment this is the review gate — replacement actions on stateful resources like RDS or EBS are flagged here before any disruption.
03

Add an EC2 instance with cross-referenced resources

EC2 is harder than S3 because the instance depends on three other resources in the same template (AMI, Security Group, Subnet). The lab introduces two techniques to solve that cleanly.

3a. SSM-backed AMI parameter (region-agnostic)

Hard-coding an AMI ID would tie the template to a single region. Instead, the AMI is resolved at deploy time from the SSM public parameter store:

Parameters:
  AmazonLinuxAMIID:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

The same template will now resolve to the correct Amazon Linux 2 AMI in any region CloudFormation deploys to. The AMI ID never appears in the template itself.

3b. The EC2 resource (5 properties, three !Refs)

AppServer:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: !Ref AmazonLinuxAMIID
    InstanceType: t3.micro
    SecurityGroupIds:
      - !Ref AppSecurityGroup
    SubnetId: !Ref PublicSubnet
    Tags:
      - Key: Name
        Value: App Server
  • SecurityGroupIds is a list — even with a single SG, the YAML uses the hyphen list syntax. This was the most common gotcha flagged in the lab tips.
  • The three !Ref calls (AmazonLinuxAMIID, AppSecurityGroup, PublicSubnet) create implicit dependencies. CloudFormation guarantees the referenced resources exist before EC2 launch.
  • Uploaded the revised template via Update stack → Replace current template. The change set showed one Add action for the EC2 instance and no replacements on existing resources. Executed and the stack reached UPDATE_COMPLETE.
No screenshot for this task: the lab transcript captures the change set event for S3 (Task 2) and the delete (Task 4). The EC2 addition is documented above with the exact YAML committed to the template — that is the source of truth for what was deployed.
04

Delete the stack — automatic teardown

  • In the CloudFormation console, selected the Lab stack and clicked Delete. Confirmed in the modal.
  • Stack moved to DELETE_IN_PROGRESS. CloudFormation deleted resources in reverse dependency order: EC2 instance and S3 bucket first, then route table association, route, route table, IGW attachment, IGW, subnet, security group, and finally the VPC.
  • The deletion timeline in the console showed each of the 8 networking resources decommissioning in sequence between 06:01 and 06:08 UTC-0500. After the timeline finished, the stack disappeared from the active stacks list.
Why this is the killer feature of IaC: a single click reverses everything the stack created, in the correct order, with no manual tracking. Compare to deleting these resources manually — you would hit dependency errors deleting the VPC first because of the attached IGW, the subnet because of the route table association, and so on.

CloudFormation via the AWS CLI

The lab was performed through the console. For reference and future runs, here are the equivalent AWS CLI commands that produce the same lifecycle.

deploy

Create the stack (Task 1)

aws cloudformation create-stack \
  --stack-name Lab \
  --template-body file://task1.yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --region us-west-2

# Block until the stack reaches CREATE_COMPLETE
aws cloudformation wait stack-create-complete \
  --stack-name Lab --region us-west-2

# Inspect the result
aws cloudformation describe-stacks \
  --stack-name Lab --region us-west-2 \
  --query 'Stacks[0].{Status:StackStatus,Outputs:Outputs}'
diff

Preview an update via change set (Tasks 2 & 3)

# Create a change set against the running stack
aws cloudformation create-change-set \
  --stack-name Lab \
  --change-set-name add-s3-bucket \
  --template-body file://task2.yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --region us-west-2

# Inspect what will change before applying
aws cloudformation describe-change-set \
  --stack-name Lab \
  --change-set-name add-s3-bucket \
  --region us-west-2 \
  --query 'Changes[*].ResourceChange.{Action:Action,Logical:LogicalResourceId,Type:ResourceType}'

# Apply the change set
aws cloudformation execute-change-set \
  --stack-name Lab \
  --change-set-name add-s3-bucket \
  --region us-west-2

aws cloudformation wait stack-update-complete \
  --stack-name Lab --region us-west-2
events

Watch the lifecycle in real time

# Stream the most recent stack events (timestamps + resource status)
aws cloudformation describe-stack-events \
  --stack-name Lab --region us-west-2 \
  --query 'StackEvents[*].{T:Timestamp,Status:ResourceStatus,Logical:LogicalResourceId,Reason:ResourceStatusReason}' \
  --output table

# List resources currently managed by the stack
aws cloudformation list-stack-resources \
  --stack-name Lab --region us-west-2 \
  --output table
delete

Tear down the stack (Task 4)

aws cloudformation delete-stack \
  --stack-name Lab --region us-west-2

aws cloudformation wait stack-delete-complete \
  --stack-name Lab --region us-west-2

# Confirm the stack is gone (DELETE_COMPLETE stacks are filtered out by default)
aws cloudformation list-stacks \
  --region us-west-2 \
  --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \
  --query 'StackSummaries[?StackName==`Lab`]'

Key Learnings

What Was Actually Learned

How to structure a CloudFormation template into Parameters, Resources, and Outputs.
How a change set previews a stack update before it touches any live resource.
Use AWS::SSM::Parameter::Value<…> to make a template region-agnostic by resolving the AMI at deploy time.
Cross-reference resources within a template using !Ref; CloudFormation derives the dependency order from the references.
SecurityGroupIds is a YAML list — even a single SG must use the hyphen syntax.
Stack deletion is the inverse of creation: CloudFormation removes resources in reverse dependency order automatically.

Technical Conclusion

CloudFormation closes the gap between a manually-clicked AWS console and a reproducible, versionable deployment. The same YAML file that built this lab can be committed to a git repository, peer-reviewed, deployed into multiple regions, and deleted on demand — identically each time.

The two non-obvious primitives are change sets (a declarative dry run before any change is applied) and SSM Parameter references (which decouple region-specific data from the template itself). Together they make stacks safe to update in production and portable across regions.

The Optional verification steps (browsing the VPC, S3, and EC2 consoles after each task) were not performed — the Resources tab inside the CloudFormation console already lists every managed object with its physical ID and link.