How to Launch an EC2 Instance in a Custom VPC Using CloudFormation

Create a Custom VPC and Launch an EC2 Instance with CloudFormation

How to Launch an EC2 Instance in a Custom VPC Using CloudFormation

If you are a developer, network, or cloud engineer, you understand the challenges of constantly setting up different tools on the cloud to ensure consistent and secure environment deployment and avoid scaling challenges on web applications.

With Infrastructure as Code (IaC) tools like AWS CloudFormation, you can automate the deployment and configuration of cloud resources. This enables consistent and secure setups while reducing manual tasks. CloudFormation allows you to define tool specifications and create a custom Virtual Private Cloud (VPC) using templates in JSON or YAML formats.

CloudFormation is relevant when you want to create a more complex infrastructure without moving from dashboard to dashboard on the AWS management console. As long as the AWS resources you are trying to create allow that function, using IaC, you can add them to CloudFormation. Instead of moving from console to console, you predefine what resources you need, and they are created automatically.

In this guide, you will learn to use AWS CloudFormation to create a custom VPC and launch an EC2 instance within it. This will be done through YAML templates that automate the process. Each step is broken down for clarity.

What You Will Learn and Build

  • Understand how to define and set up a Virtual Private Cloud with CloudFormation.

  • Learn basic networking concepts like subnets, CIDR blocks, and internet gateways.

  • Get hands-on writing simple YAML templates to deploy AWS resources.

  • Deploy an Amazon EC2 instance within your custom VPC.

Prerequisites

  • Basic cloud and networking knowledge.

  • An AWS account to access the management console.

  • Beginner understanding of YAML (optional).

What is a Custom VPC?

Before defining what a custom VPC is, it is important to understand how the cloud works. Think of the cloud as a bustling industrial hub, like Wall Street, where major public cloud providers such as Amazon, Google, and Microsoft rent offices and resources to various organizations for their business operations. Amazon, for example, has offices in numerous locations, and once you sign up, you gain access to their facilities and interconnected network to enable you to interact with your customers.

However, these facilities are public, creating a high tendency for a data breach. You decide that to protect your company’s data, you will need a higher level of security. Amazon provides the option of renting a private office space instead of working in the general office shared with others. In your own office, you have a private network. Although you still have to share utilities like the elevator, only authorized people have access to your office. This private office space represents a Virtual Private Cloud (VPC)—a secure, isolated section of the cloud within Amazon's infrastructure that protects your applications from the broader public space.

When you request for VPC, you have the option of using the default VPC from Amazon, or you can create a custom VPC that meets your needs. Configuring the VPC can be likened to employing a contractor and completely redesigning the layout of the office instead of moving into the pre-arranged office. Your contractor in this scenario is CloudFormation. Using CloudFormation templates in YAML, you can write code that specifies which AWS resources you need for your project. This code format solves the problem of moving from console to console provisioning resources. You can automate designing and provisioning the office (your VPC) in less time.

Setting Up Your Custom VPC with CloudFormation

Now that you understand what a custom VPC is and how it helps secure and organize your resources, let’s get hands-on. In this section, you will configure and provision a custom Virtual Private Cloud (VPC) using AWS CloudFormation.

Architecture

By using CloudFormation, you can automate the creation of a tailored VPC, complete with subnets and gateways, without manual configurations.

Step 1: Define Parameters

This guide follows the structure recommended in AWS's official CloudFormation documentation, adapting it for simplicity and beginner-friendly understanding.

To begin, open your text editor and create a YAML file where you will define your VPC parameters.

Note: Each step in this process builds on the last, so each code snippet continues in the same YAML file.

Use the following code snippet as your starting point:

AWSTemplateFormatVersion: '2010-09-09'

Description: This template creates a custom VPC with a single public subnet and launches an EC2 instance in that subnet.

Parameters:
  VpcCIDR:
    Description: The CIDR block for the VPC (e.g., 10.0.0.0/16).
    Type: String
    Default: 10.0.0.0/16

  SubnetCIDR:
    Description: The CIDR block for the VPC (e.g., 10.0.0.0/16).
    Type: String
    Default: 10.0.0.0/24

  InstanceType: 
    Description: The EC2 instance type (e.g., t2.micro). 
    Type: String 
    Default: t2.micro 
    AllowedValues: 
      - t2.micro 
      - t2.small 
      - t2.medium 
    ConstraintDescription: Must be a valid EC2 instance type.

In the code above, after adding the format version and description of this template, you have created what parameters whoever uses this template will choose from. Parameters are important because they allow you to reuse this template in different scenarios without changing the code.

For example, if you launch this template with InstanceType parameter, you have the option of choosing between a t2 micro, small, or medium according to your application demands. With Parameters, you just select the size when deploying the stack, leaving the template code untouched.

Additionally, any user of your template can manually input the Classless Inter-Domain Routing (CIDR) range that will best suit their project or choose to go with the default. To learn more about CIDR ranges review these guide on what a CIDR is and how to calculate them.

Step 2: Create Your VPC and Use the Parameters in Resources

Now, in the Resources section, define your VPC using the code below:

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:
        Ref: VpcCIDR
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        - Key: Name
          Value: CustomVPC

What the line of code above does is that it specifies to AWS that you need a custom VPC whose CIDR ranges you have already specified in the Parameters section, and AWS should reference VpcCIDR for its range.

This code also instructs AWS to ensure that the resources (like S3 or EC2) inside this VPC instance can communicate with each other using their domain names (e.g., my-bucket.s3.amazonaws.com).

Additionally, setting EnableDnsHostnames to true allows EC2 instances that you launch in the VPC with a public IP to automatically get a public DNS name (e.g., ec2-203-0-113-25.compute-1.amazonaws.com).

Lastly, although optional, the tags act as an identification and are very helpful in cases of multiple VPCs.

Step 3: Assign an Internet Gateway

By default, VPCs do not allow internet traffic, but some applications in your VPC may need to communicate with resources outside of your VPC, like in the case of hosting a public-facing web application with NGINX. To allow your EC2 instance to communicate with the internet, you will need to add an Internet Gateway to your VPC and attach it. First, you will create an Internet Gateway with this code:

InternetGateway:
  Type: AWS::EC2::InternetGateway
  Properties:
    Tags:
      - Key: Name
        Value: MyInternetGateway

AttachGateway:
  Type: AWS::EC2::VPCGatewayAttachment
  Properties:
    VpcId: !Ref MyVPC
    InternetGatewayId: !Ref InternetGateway

This block of code creates an Internet Gateway and connects it to your VPC, enabling outbound and inbound Internet traffic.

Step 4: Add Subnets to Organize Your Network

From the architecture diagram, you will notice that this project requires a subnet. A subnet divides your VPC into smaller sections, just like how an office floor plan may separate meeting rooms from workstations and assign addresses to those sections to enable resources from other subnets or internet traffic to find that workstation or room.

There are different types of subnets, but the more common types are the private and public subnets, and you can create as many subnets as you might need. To create a subnet, copy the code below to the Resources section of your template file.

PublicSubnet:  
    Type: AWS::EC2::Subnet  
    Properties:  
      VpcId: !Ref MyVPC  
      CidrBlock: !Ref SubnetCIDR 
      AvailabilityZone: us-east-1a  
      MapPublicIpOnLaunch: true  
      Tags:  
        - Key: Name  
          Value: PublicSubnet

Subnets determine which resources can be public-facing or private, giving you better control over traffic flow and security. Now, you have created your subnet and connected it to the VPC you built earlier using !Ref MyVPC. Additionally, you referenced the subnet CIDR parameter you added in Step 1 to define its range of IP addresses.

You will also notice a section in your code labelled "Availability Zone". An availability zone is like an independent data centre within an AWS region where your subnet operates.

Subnets are tied to a single availability zone, which allows you to control exactly where your resources are located. For a deeper dive into availability zones and their importance in ensuring high availability and fault tolerance, check out the AWS documentation on Availability Zones.

Lastly, with MapPublicIpOnLaunch set to true, you have made IP addresses available for any EC2 instance you launch on this subnet. This setting is essential for resources that need to be publicly accessible, like web servers or application servers.

Step 5: Create a Route Table for Internet Traffic

In step 3, you created a gateway to allow resources within your VPC to interact with the internet. In addition to this gateway, you need a technology that directs your subnet on the path to follow to the internet gateway and that technology is a Route table.

To create a Route table, add the code below to your YAML file:

PublicRouteTable:
  Type: AWS::EC2::RouteTable
  Properties:
    VpcId: !Ref MyVPC
    Tags:
      - Key: Name
        Value: PublicRouteTable

PublicRoute:
  Type: AWS::EC2::Route
  Properties:
    RouteTableId: !Ref PublicRouteTable
    DestinationCidrBlock: 0.0.0.0/0
    GatewayId: !Ref InternetGateway

The first section of your code creates a public route table, which is a container for routing rules within your VPC. The PublicRoute section adds a rule that sends all internet traffic (DestinationCidrBlock: 0.0.0.0/0) to the internet gateway (GatewayId: !Ref InternetGateway) you created earlier.

Here, your PublicRouteTable is the "roadmap" for traffic inside your VPC. It sets up the pathways (roads) for your subnet to send and receive traffic. Without a route table, your subnet is like a closed room with no way to get in or out. After creating this pathway, the PublicRoute defines how much access your subnet has to the outside world, whether it’s just the local network, a specific destination, or the whole internet.

Step 6: Associate Your Route Table to Your Subnet

Now that you have created a route to the internet, you need to connect that route to your public subnet. Otherwise, if you try to access your EC2 instance from its domain name, you will receive an error. Associate your route table to your subnet with the code below:

SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

Step 7: Create a Security Group for Your EC2 Instance

Security groups are firewalls that control access in and out of your EC2 instance. They allow specific traffic (like HTTPS, HTTP, and SSH) and offer an added layer of protection for resources in your subnet. Attach the code snippet below to create your security group.

MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP, HTTPS, and SSH traffic
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0 # Allows SSH from anywhere (adjust for security)
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0 # Allows HTTP traffic
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0 # Allows HTTPS traffic
      Tags:
        - Key: Name
          Value: MySecurityGroup

In your code, you have referenced myVPC as the VPC this security group should be added. You have also stated that this security group should allow inbound traffic from HTTP, HTTPS, and SSH in the SecurityGroupIngress section.

Step 8: Launch Your EC2 Instance

So far, you have configured your VPC, subnet, and necessary firewalls for security. The next step is to add resources to this subnet. In this case, you will be adding an EC2 instance. Before adding your EC2 instance, you need to complete the steps below:

  1. Amazon EC2 instances usually need a key pair. Before adding any other code to your YAML file. Log in to the Amazon Management Console in the same region as the availability zone you specified in your subnet, navigate to the EC2 dashboard and create a key pair. For a step-by-step guide on how to create an EC2 key pair, click the link. Take note of the name of your key pair as you will be adding that to your code.

  2. Next, you want to copy the AMI ID of the machine image you prefer on the Launch Instances tab of the Amazon EC2 dashboard.

After creating your key pair and copying the AMI ID of your machine image, add this code to continue with launching an EC2 instance from your template.

  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: 
        Ref: InstanceType
      ImageId: "ami-0866a3c8686eaeeba" # Replace with the desired AMI
      SubnetId: 
        Ref: PublicSubnet
      KeyName: "NewKeyPair" # Replace with name of your key pair
      SecurityGroupIds:
      - !Ref MySecurityGroup
      Tags:
        - Key: Name
          Value: MyInstance

You can copy the complete code of this template from this GitHub repository.

Step 9: Create a Stack in CloudFormation

There are a few more steps you need to complete before all your resources are fully operational on the Internet.

  1. Search for CloudFormation on the search bar on the Management console and open the Dashboard.

  2. On that dashboard, click on Create Stack to open a new tab for uploading your YAML file. In this tab, you will see the Prerequisite - Prepare template section. In this section, leave the selection as default and move to the Specify template section.

  1. In the Specify template section, select Upload a template file. This command opens up another section where you upload your file.

  1. After uploading your file, click on Next. CloudFormation then validates your file before taking you to the next section. If your file does not strictly adhere to the correct YAML syntax, you will have an error.

  2. After validating your file, a new section opens up where you Specify your stack details. In this section, you will also see the parameters you specified in Step 1.

Name your file and leave every other setting as default and continue to the next section.

  1. Leave all sections under Configure Stack Options in their default state and click Next.

  2. Review your stack to ensure all the settings are in the state you prefer and submit.

  3. On submitting, you will get redirected to a new tab showing that your stack creation is in progress.

After a few minutes, CloudFormation will create your stack successfully.

To confirm that all your resources are working as they should, open VPC in the management console and check that the VPC you provisioned is on the list of VPCs.

Additionally, you can check other resources like your EC2 instance to see if it is running properly.

Troubleshooting and Helpful Resources

Even with the best plans, errors can happen when working with CloudFormation templates. Some common challenges include invalid AMI IDs, mismatched resources like subnets and VPCs, or incorrect security group configurations. The AWS CloudFormation Events tab is your go-to for diagnosing errors during stack creation—it provides real-time logs on what went wrong and why.

If you are looking to deepen your understanding of networking concepts like subnets, CIDR blocks, or security groups, check out the official AWS Networking Documentation. This guide complements what this guide covered and dives deeper into topics not fully explored here.

By understanding how to troubleshoot and where to find more resources, you will be better at refining your templates and scaling your cloud infrastructure.