In this article, I'll walk through how I set up a scalable and modular virtual network architecture with AWS VPC. This is part of my hands-on DevSecOps learning journey. This guide is perfect for those who are just starting with AWS networking. Diagram at the bottom.
Prerequisites
- AWS Account
- HTML web app code
Pre-deployment
- AWS CLI
- Install nginx
- install Git
- Cloudwatch Agent
- Push custom memory metrics to CloudWatch.
- AWS SSM agent
Deployment
I started by creating a VPC for the bastion host in the diagram. I aimed to keep my application servers hidden from the internet. Instead, all admin access would go through a single controlled entry point (bastion host). So in this VPC, I created a public subnet and attached an Internet Gateway, allowing me to SSH into the bastion instance. How I did it:
#create vpc baston vpc
aws ec2 create-vpc --cidr-block 192.168.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=vpc-baston}]'
#create a public subnet for this VPC
aws ec2 create-subnet --vpc-id vpc-id --cidr-block 192.168.1.0/24 --availability-zone us-east-1a --tag-specification 'ResourceType=subnet,Tags=[{Key=Name,Value=ubnet-name}]'
#create and attach internet-gateway
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=igw-name}]'
aws ec2 attach-internet-gateway --vpc-id vpc-id --internet-gateway-id igw-id
Next, I created the second VPC where the app servers will be located. Unlike the bastion VPC, this VPC will have both private and public subnets. The public subnets will host the load balancer and NAT gateway, while the private subnets will have the application servers. How I went on about it:
#create vpc app vpc
aws ec2 create-vpc --cidr-block 172.32.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=app-vpc}]'
#create two public subnets
aws ec2 create-subnet --vpc-id vpc-id --cidr-block 172.32.1.0/24 --availability-zone us-east-1a --tag-specification
aws ec2 create-subnet --vpc-id vpc-id --cidr-block 172.32.2.0/24 --availability-zone us-east-1b --tag-specification
#create two private subnets
aws ec2 create-subnet --vpc-id vpc-id --cidr-block 172.32.10.0/24 --availability-zone us-east-1a --tag-specification
aws ec2 create-subnet --vpc-id vpc-id --cidr-block 172.32.20.0/24 --availability-zone us-east-1b --tag-specification
#create and attach Internet Gateway to vpc
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=igw-name}]'
aws ec2 attach-internet-gateway --vpc-id vpc-id --internet-gateway-id igw-id
#create a nat-gateway to the public subnet.. created a elastic ip first
aws ec2 create-nat-gateway --subnet-id subnet-id --allocation-id id
Next, I created two route tables, one for the public subnets and another for the private subnets. Edited the public subnets' route table route to take the internet gateway for internet connection and public (Dev) access and edited the private subnet route table routes to take the NAT gateway for outbound internet. (used the console)
Further, I created a transit gateway to establish communication between the 2 VPCs. I also made sure to create the transit gateway for the VPCs, choosing all public subnets from the VPCs. I then edited the public subnets' route table routes to take the IPs from the VPC, i.e., the destination as IPS and targets as the transit gateways. (used the console)

Route table for App VPC subnets
I launched an EC2 in the Bastion VPC, assigned an Elastic IP, and allowed SSH on port 22.
Moreover, I made an AMI to be used by the EC2; the reason is I don't want to download the same thing on 3 or more EC2 instances. I install all that is stated in pre-deployment. I then launched the template into 2 instances for the app. Now all working, I needed to add the auto scaling group to handle auto-scaling.
Next, I created an Auto Scaling Group (ASG) across both private subnets to ensure high availability. This way, even if one availability zone goes down, the application would still remain accessible.
I attached a target group configured with the “Instances” target type, listening on port 80, and set the health check path to /. Once the instances began registering and I saw them transition to a healthy state, I knew the backend layer was properly set up.
With that in place, I moved on to creating an internet-facing application load balancer. I deployed it in the public subnets of the application VPC and configured its security group to allow HTTP traffic from anywhere (0.0.0.0/0). I then added a listener on port 80 and pointed it to the target group, allowing incoming requests to be routed to the EC2 instances in the private subnets.
Finally, I configured DNS using Amazon Route 53 by creating an A record (Alias) that points directly to the load balancer. This allowed me to access the application using a domain name instead of the ALB DNS endpoint.
Mistakes I did
- Missing transit gateway route. I couldnt SSH from the Bastion into App VPC
- 503 Error. No targets in target group'
- Couldnt make the golden AMI.. I didnt understand in the first place
- Nginx default page.. browser cached the nginx default page. I did hard reloading
Archichecture
Conclusion
Built a Production-Style AWS Architecture (And Broke It 5 Times First). This project was really fire; it helped me to understand VPC, bastion hosts, IPs, subnetting, etc. Whats next? I'm starting Terraform next week.
full project on https://github.com/NotHarshhaa/DevOps-Projects/tree/master/DevOps-Project-02


Top comments (0)