<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Purity Chepkemoi</title>
    <description>The latest articles on DEV Community by Purity Chepkemoi (@puritychepkemoi).</description>
    <link>https://dev.to/puritychepkemoi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4022492%2F28362ff2-1abd-4dbe-bbff-05f82669984f.jpg</url>
      <title>DEV Community: Purity Chepkemoi</title>
      <link>https://dev.to/puritychepkemoi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/puritychepkemoi"/>
    <language>en</language>
    <item>
      <title>Troubleshooting Public vs Private IP Addresses in AWS: What I Learned from an AWS re/Start Lab</title>
      <dc:creator>Purity Chepkemoi</dc:creator>
      <pubDate>Thu, 08 Oct 2026 16:33:31 +0000</pubDate>
      <link>https://dev.to/puritychepkemoi/troubleshooting-public-vs-private-ip-addresses-in-aws-what-i-learned-from-an-aws-restart-lab-5j4</link>
      <guid>https://dev.to/puritychepkemoi/troubleshooting-public-vs-private-ip-addresses-in-aws-what-i-learned-from-an-aws-restart-lab-5j4</guid>
      <description>&lt;p&gt;Networking has been one of the areas I've been trying to understand better during my AWS re/Start journey.&lt;/p&gt;

&lt;p&gt;One lab helped me connect several networking concepts to an actual troubleshooting scenario: &lt;strong&gt;two EC2 instances were in the same subnet, but one could reach the internet while the other could not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of simply defining public and private IP addresses, the lab asked me to investigate what could be causing the difference.&lt;/p&gt;

&lt;p&gt;This is how I approached it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario
&lt;/h2&gt;

&lt;p&gt;I was given a customer support scenario involving a VPC with a CIDR range of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;10.0.0.0/16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside the VPC were two EC2 instances:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance A&lt;/li&gt;
&lt;li&gt;Instance B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both instances were in the same subnet and had similar configurations.&lt;/p&gt;

&lt;p&gt;However:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance A could not reach the internet.&lt;/li&gt;
&lt;li&gt;Instance B could reach the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer also wanted to create another VPC using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;12.0.0.0/16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and wanted to know whether that would cause any issues.&lt;/p&gt;

&lt;p&gt;So there were two questions to investigate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why were the two EC2 instances behaving differently?&lt;/li&gt;
&lt;li&gt;Should &lt;code&gt;12.0.0.0/16&lt;/code&gt; be used as the CIDR range for the new VPC?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Looking at the Architecture
&lt;/h2&gt;

&lt;p&gt;Before looking for an answer, I first tried to understand the architecture.&lt;/p&gt;

&lt;p&gt;The VPC contained the two EC2 instances in the same subnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajovs13xznkrntslwi9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajovs13xznkrntslwi9k.png" alt=" " width="720" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I started thinking about the different things that could affect connectivity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What IP addresses did the instances have?&lt;/li&gt;
&lt;li&gt;Did both instances have public IP addresses?&lt;/li&gt;
&lt;li&gt;What routes were associated with the subnet?&lt;/li&gt;
&lt;li&gt;What did the security group allow?&lt;/li&gt;
&lt;li&gt;Was there a path for traffic to leave the VPC?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was useful because it changed the way I looked at the problem.&lt;/p&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;p&gt;"One EC2 instance works and the other doesn't."&lt;/p&gt;

&lt;p&gt;I started thinking:&lt;/p&gt;

&lt;p&gt;"What is different about the network path to each instance?"&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Compare the IP Addresses
&lt;/h1&gt;

&lt;p&gt;The first thing I checked was the networking information for both EC2 instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instance A
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz6xt9n5x2uuzqdnvpa48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz6xt9n5x2uuzqdnvpa48.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instance A had a &lt;strong&gt;private IP address only&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instance B
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq3j9hca87673bb335wmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq3j9hca87673bb335wmd.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instance B had both a &lt;strong&gt;private IP address and a public IP address&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This was the first major difference I found.&lt;/p&gt;

&lt;p&gt;Both instances had private IP addresses because resources inside a VPC use private addressing for communication.&lt;/p&gt;

&lt;p&gt;The important difference was that Instance B also had a public IP address.&lt;/p&gt;

&lt;p&gt;That gave me a possible explanation for why I could connect to Instance B using SSH from outside the VPC, while I couldn't directly connect to Instance A using its private IP address.&lt;/p&gt;

&lt;p&gt;But an IP address is only one part of the story.&lt;/p&gt;

&lt;p&gt;So what else would I check?&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Check the Route Table
&lt;/h1&gt;

&lt;p&gt;The lab itself focused mainly on the difference between the public and private IP addresses.&lt;/p&gt;

&lt;p&gt;If I were troubleshooting this in a real AWS environment, the next thing I would check is the &lt;strong&gt;route table associated with the subnet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A route table determines where network traffic should go.&lt;/p&gt;

&lt;p&gt;For internet-bound traffic from a subnet that uses an Internet Gateway, I would expect to see a route similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.0.0.0/0 → Internet Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0.0.0.0/0&lt;/code&gt; represents traffic destined for addresses outside the VPC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghptge6ltbkf89slpuwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghptge6ltbkf89slpuwz.png" alt=" " width="798" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This check is important because having a public IP address does not, by itself, guarantee internet connectivity.&lt;/p&gt;

&lt;p&gt;There also needs to be an appropriate network path.&lt;/p&gt;

&lt;p&gt;This is one of the things I'm learning about cloud networking: &lt;strong&gt;connectivity is usually the result of several pieces working together.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: Check the Security Group
&lt;/h1&gt;

&lt;p&gt;Another thing I would check is the security group attached to the EC2 instance.&lt;/p&gt;

&lt;p&gt;A security group acts as a virtual firewall for an EC2 instance. It controls which traffic is allowed to reach the instance and which traffic is allowed to leave it.&lt;/p&gt;

&lt;p&gt;For an SSH connection, I would look for an inbound rule allowing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP — Port 22 — SSH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the lab environment, the SSH rule used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as the source.&lt;/p&gt;

&lt;p&gt;This means SSH traffic was allowed from any IPv4 address.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc4j55df95dykcrp9qis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc4j55df95dykcrp9qis.png" alt=" " width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That helped me understand an important distinction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A public IP address does not automatically mean that an instance is accessible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The network path and security rules also have to allow the traffic.&lt;/p&gt;

&lt;p&gt;For a real production environment, I would not leave SSH open to &lt;code&gt;0.0.0.0/0&lt;/code&gt; unnecessarily. I would restrict access to trusted sources or consider a more secure management approach.&lt;/p&gt;

&lt;h1&gt;
  
  
  So What Was Actually Different?
&lt;/h1&gt;

&lt;p&gt;After looking at the IP configuration, the main difference between the two instances was their addressing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Instance A&lt;/th&gt;
&lt;th&gt;Instance B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Private IP&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public IP&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct SSH from outside VPC&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Possible*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Assuming the required routing and security rules are correctly configured.&lt;/p&gt;

&lt;p&gt;The fact that both instances were in the same subnet did &lt;strong&gt;not&lt;/strong&gt; mean they had identical connectivity.&lt;/p&gt;

&lt;p&gt;Instance A had only a private IP address, so I could not directly connect to it from outside the VPC using that private address.&lt;/p&gt;

&lt;p&gt;Instance B had a public IP address, giving it an address that could be reached from outside the VPC, provided the rest of the configuration allowed the connection.&lt;/p&gt;

&lt;p&gt;That was the key finding from the lab.&lt;/p&gt;

&lt;h1&gt;
  
  
  What If an EC2 Instance Has Only a Private IP?
&lt;/h1&gt;

&lt;p&gt;This was one of the parts of the lab that helped me understand private IP addresses more clearly.&lt;/p&gt;

&lt;p&gt;When I first think about an instance with only a private IP, it's tempting to think:&lt;/p&gt;

&lt;p&gt;"If it doesn't have a public IP, it can't access the internet."&lt;/p&gt;

&lt;p&gt;But that's not necessarily true.&lt;/p&gt;

&lt;p&gt;There are two separate questions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Can it access the internet?
&lt;/h3&gt;

&lt;p&gt;Yes, depending on how the network is designed.&lt;/p&gt;

&lt;p&gt;For example, an EC2 instance in a private subnet can use a &lt;strong&gt;NAT Gateway&lt;/strong&gt; for outbound internet access.&lt;/p&gt;

&lt;p&gt;A simplified traffic flow can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2
 ↓
Private Subnet
 ↓
NAT Gateway
 ↓
Internet Gateway
 ↓
Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EC2 instance doesn't need its own public IP for this type of outbound connectivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can someone on the internet connect directly to it?
&lt;/h3&gt;

&lt;p&gt;Not through its private IP address.&lt;/p&gt;

&lt;p&gt;A private IP is intended for communication within the VPC or through connected private networks.&lt;/p&gt;

&lt;p&gt;This is actually useful when you don't want a server to be directly exposed to the public internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  How could you access it securely?
&lt;/h3&gt;

&lt;p&gt;One option is &lt;strong&gt;AWS Systems Manager Session Manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Session Manager can allow administrators to manage an EC2 instance without giving it a public IP or opening inbound SSH access to the internet.&lt;/p&gt;

&lt;p&gt;Another traditional approach is a &lt;strong&gt;bastion host&lt;/strong&gt;, where an administrator connects to a controlled host in a public subnet and then uses it to reach instances in private subnets.&lt;/p&gt;

&lt;p&gt;The distinction I took away was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A private IP doesn't mean "no internet." It means the instance isn't directly reachable from the public internet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was an important distinction for me.&lt;/p&gt;

&lt;h1&gt;
  
  
  What About the Customer's &lt;code&gt;12.0.0.0/16&lt;/code&gt; Question?
&lt;/h1&gt;

&lt;p&gt;The second part of the scenario was about choosing a CIDR range for a new VPC.&lt;/p&gt;

&lt;p&gt;The customer suggested:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;12.0.0.0/16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To evaluate this, I needed to understand the private IPv4 address ranges commonly used for private networks.&lt;/p&gt;

&lt;p&gt;They are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing VPC's:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;10.0.0.0/16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;falls within the private address space.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;12.0.0.0/16&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;Because of that, I would not recommend using &lt;code&gt;12.0.0.0/16&lt;/code&gt; as the VPC CIDR range.&lt;/p&gt;

&lt;p&gt;Instead, I would choose an appropriate private CIDR range and consider how the network might grow and connect to other networks in the future.&lt;/p&gt;

&lt;p&gt;This is important because network design decisions made at the beginning can affect future connectivity.&lt;/p&gt;

&lt;p&gt;For example, poorly planned or overlapping address ranges can cause problems when connecting different networks.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I Learned From the Lab
&lt;/h1&gt;

&lt;p&gt;The biggest thing I took away from this lab wasn't simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Public IP = accessible, private IP = not accessible."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's more nuanced than that.&lt;/p&gt;

&lt;p&gt;I started to think about connectivity as a series of questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP address
     ↓
Routing
     ↓
Security rules
     ↓
Network path
     ↓
Connectivity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something isn't working, I can work through those layers instead of immediately assuming that the EC2 instance itself is the problem.&lt;/p&gt;

&lt;p&gt;I also learned that two instances can be in the &lt;strong&gt;same subnet&lt;/strong&gt; and still have different connectivity because their configurations can differ.&lt;/p&gt;

&lt;p&gt;And perhaps most importantly, I learned that troubleshooting isn't just about knowing definitions.&lt;/p&gt;

&lt;p&gt;It's about knowing &lt;strong&gt;what to check next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Networking is still one of the areas I'm working on, so I wouldn't say I've mastered it after one lab.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;p&gt;If I had to explain the main lesson from this lab to another beginner, I'd put it simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A private IP is an internal address, while a public IP provides an address that can be reached from outside the private network.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But whether an EC2 instance can actually communicate depends on more than its IP address.&lt;/p&gt;

&lt;p&gt;You also need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;Security groups&lt;/li&gt;
&lt;li&gt;Internet Gateways&lt;/li&gt;
&lt;li&gt;NAT&lt;/li&gt;
&lt;li&gt;The direction of the traffic&lt;/li&gt;
&lt;li&gt;How the resource is intended to be accessed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the part I found most valuable.&lt;/p&gt;

&lt;p&gt;I started with a simple question about two EC2 instances and ended up seeing how several AWS networking concepts connect together.&lt;/p&gt;

&lt;p&gt;And I'm still learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part of my AWS re/Start journey
&lt;/h3&gt;

&lt;p&gt;I originally documented this lab as part of my &lt;strong&gt;AWS re/Start learning journey on Medium&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Read the original article on Medium → &lt;a href="https://medium.com/@pyuah16/my-aws-re-start-journey-part-2-networking-public-vs-private-ip-addresses-183f1c73143f" rel="noopener noreferrer"&gt;https://medium.com/@pyuah16/my-aws-re-start-journey-part-2-networking-public-vs-private-ip-addresses-183f1c73143f&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>networking</category>
      <category>devops</category>
    </item>
    <item>
      <title>CloudFormation: A Hands-On Journey Through Compute, Nesting, and Custom Resources</title>
      <dc:creator>Purity Chepkemoi</dc:creator>
      <pubDate>Fri, 10 Jul 2026 19:20:47 +0000</pubDate>
      <link>https://dev.to/puritychepkemoi/cloudformation-a-hands-on-journey-through-compute-nesting-and-custom-resources-117h</link>
      <guid>https://dev.to/puritychepkemoi/cloudformation-a-hands-on-journey-through-compute-nesting-and-custom-resources-117h</guid>
      <description>&lt;p&gt;As part of my intensive DevSecOps training with ParoCyber, I’ve been deep-diving into AWS CloudFormation.&lt;br&gt;
For these exercises, I am working through the &lt;a href="https://github.com/samuel-nartey/devops-labs/blob/main/aws/lab-03-iam-vpc-ec2-foundations/Cloudformation-beginner-to-intermediate.md" rel="noopener noreferrer"&gt;CloudFormation Mastery Lab Documentation&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;I am writing this article to document my personal walkthrough, observations, and how I tackled the challenges along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 3 Levels At A Glance&lt;/strong&gt;&lt;br&gt;
Here is a quick overview of what we are building and what each stage demonstrates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 5&lt;/strong&gt; — Compute &amp;amp; Wiring: Moving beyond static resources to deploy a live, bootstrapped Apache web server on EC2 wrapped inside localized Security Groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 6&lt;/strong&gt; — Composition (Nested Stacks): Breaking down monolithic templates into a clean, reusable modular architecture managed by a single Parent stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 7&lt;/strong&gt; — Dynamic Intelligence (Custom Resources): Extending CloudFormation's native capabilities by using a Python Lambda function to query customized AMI filters dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 5 — Compute &amp;amp; Wiring: EC2, Security Groups &amp;amp; Intrinsic Functions
&lt;/h2&gt;

&lt;p&gt;I saved the code as webserver.yaml and deployed it via the CloudFormation console using t3.micro.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2vs1jbzoneehv8jox9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2vs1jbzoneehv8jox9d.png" alt="create" width="800" height="413"&gt;&lt;/a&gt;&lt;br&gt;
The public IP in the Outputs tab is:&lt;strong&gt;18.234.230.243&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrmia6eaivu5qflr6jy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjrmia6eaivu5qflr6jy0.png" alt="output" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observation Point&lt;/strong&gt;&lt;br&gt;
Once deployed, I updated the stack to switch the InstanceType from t3.micro to t3.small.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzwistrvut9ch2an5i4zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzwistrvut9ch2an5i4zq.png" alt="observation" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56lw7qo9hlp6nrnt7u4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56lw7qo9hlp6nrnt7u4s.png" alt="file" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question: Does the Public IP shown in the Outputs tab change after this update?&lt;/strong&gt;&lt;em&gt;Yes, it changes, InstanceType can be updated in place for many instance families, but the public IP is not guaranteed to persist across a stop-and-start cycle unless you're using an Elastic IP. CloudFormation performs this as a "stop, resize, start" operation under the hood for compatible type changes, and AWS reassigns a new public IPv4 address on start unless one was reserved. Refresh the Outputs tab after the update completes and compare the new PublicIp value to the one you noted before. This is why production architectures almost never rely on raw instance public IPs, they sit behind an Elastic Load Balancer (which you'll build in Level 9) or use an Elastic IP resource explicitly.&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Level 5 Challenge: Stabilizing the IP&lt;/strong&gt;&lt;br&gt;
To make the public IP persist across instance replacement , I modified the template to introduce an AWS::EC2::EIP resource and an AWS::EC2::EIPAssociation.In the Outputs tab that the public IP now stays the same&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0vdpc2yusifj8dik7r9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0vdpc2yusifj8dik7r9f.png" alt="output" width="799" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 6 — Composition: Nested Stacks
&lt;/h2&gt;

&lt;p&gt;Because nested templates must be fetched programmatically by the CloudFormation engine, I first had to spin up an S3 bucket and upload the child templates.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcg2t5yi1egly2ryw42f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcg2t5yi1egly2ryw42f.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98bfg2r4u7efy98k9rka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98bfg2r4u7efy98k9rka.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy the root stack&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd4vq1kmanklq5mkosn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd4vq1kmanklq5mkosn3.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6u85d5gkxuhyiet197dk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6u85d5gkxuhyiet197dk.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Add a third nested stack, a security.yaml template that creates a standalone AWS::EC2::SecurityGroup, upload it to the same S3 bucket, then modify compute.yaml to accept that Security Group ID as a Parameter instead of creating its own. This mirrors how real platform teams separate "network," "security," and "compute" ownership across different template authors.&lt;/em&gt;&lt;br&gt;
I added a third nested stack, &lt;strong&gt;SecurityStack&lt;/strong&gt;, using a new &lt;strong&gt;security.yaml&lt;/strong&gt; template. This template creates a standalone &lt;code&gt;AWS::EC2::SecurityGroup&lt;/code&gt; and accepts the VPC ID from the parent stack.&lt;/p&gt;

&lt;p&gt;The architecture is now split into three reusable components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;network.yaml&lt;/strong&gt;: Creates the VPC and subnet; outputs &lt;code&gt;VpcId&lt;/code&gt; and &lt;code&gt;SubnetId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;security.yaml&lt;/strong&gt;: Accepts &lt;code&gt;VpcId&lt;/code&gt;, creates the web server security group allowing HTTP (port 80), and outputs &lt;code&gt;SecurityGroupId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compute.yaml&lt;/strong&gt;: No longer creates a security group; accepts &lt;code&gt;SecurityGroupId&lt;/code&gt; and uses it in the EC2 instance &lt;code&gt;SecurityGroupIds&lt;/code&gt; property.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I uploaded &lt;strong&gt;security.yaml&lt;/strong&gt; to the same S3 bucket as the other nested templates and updated &lt;strong&gt;root.yaml&lt;/strong&gt; to add the new nested stack.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn473uxt7ze7skxl2kc18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn473uxt7ze7skxl2kc18.png" alt=" " width="799" height="406"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjg153o6976ypjypk1x38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjg153o6976ypjypk1x38.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 7 — Dynamic Intelligence: Custom Resources
&lt;/h2&gt;

&lt;p&gt;I uploaded &lt;code&gt;custom-resource.yaml&lt;/code&gt;, named the stack &lt;strong&gt;level7-custom-resource&lt;/strong&gt;, and proceeded through the wizard. On the &lt;strong&gt;Review&lt;/strong&gt; screen, I saw a new section near the bottom labeled &lt;strong&gt;"I acknowledge that AWS CloudFormation might create IAM resources."&lt;/strong&gt; I checked this box, which serves as the console's version of the &lt;code&gt;CAPABILITY_IAM&lt;/code&gt; flag, before submitting the stack.&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuyf02ay2g6djy5r8umdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuyf02ay2g6djy5r8umdr.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;br&gt;
After the stack reached &lt;strong&gt;CREATE_COMPLETE&lt;/strong&gt;, I opened the &lt;strong&gt;Outputs&lt;/strong&gt; tab and viewed the &lt;strong&gt;ResolvedAmiId&lt;/strong&gt; value that the Lambda function had automatically discovered.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wduzyj9eqmignfhoioz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wduzyj9eqmignfhoioz.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge&lt;/strong&gt;&lt;br&gt;
Modify the Lambda to also handle the Delete RequestType meaningfully, have it log the AMI ID it's "cleaning up" (even though there's nothing to actually delete in this case) to prove you understand that every Custom Resource lifecycle event needs explicit handling. Check CloudWatch Logs (search "CloudWatch" in the console, then Log groups) to confirm your log line appears after you delete the stack.&lt;br&gt;
I modified the Lambda function to handle the &lt;strong&gt;Delete&lt;/strong&gt; &lt;code&gt;RequestType&lt;/code&gt; by logging the AMI ID it was "cleaning up," even though there was no actual resource to delete. This demonstrated my understanding that every Custom Resource lifecycle event requires explicit handling. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz2pu6wrokki02pj4x7j1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz2pu6wrokki02pj4x7j1.png" alt=" " width="800" height="431"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj8augvity6epmzfhn0ed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj8augvity6epmzfhn0ed.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After deleting the stack, I checked the &lt;strong&gt;CloudWatch Logs&lt;/strong&gt; by navigating to &lt;strong&gt;CloudWatch&lt;/strong&gt; and opening the &lt;strong&gt;Logs&lt;/strong&gt; section, where I confirmed that the expected log entry had been recorded&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn7214yvxtr0nlod0jgmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn7214yvxtr0nlod0jgmq.png" alt=" " width="799" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
