Deploying a Highly Available AWS Application with Bastion Access, Transit Gateway, and Auto Scaling.
Source: Dev.to
Introduction
In this project I built a secure, highly‑available AWS architecture that mirrors real‑world production environments. By the end of this guide you will understand how to deploy:
- A bastion‑managed environment
- A private, auto‑scaled application layer
- Secure VPC‑to‑VPC communication using a Transit Gateway
- End‑to‑end logging and DNS routing
The architecture consists of two VPCs with isolated responsibilities, a Transit Gateway for private communication, public and private subnets across multiple AZs, a bastion host for secure access, an Auto Scaling Group behind a Network Load Balancer, and centralized logging/DNS routing.
VPC Design
| VPC | Purpose | CIDR Block |
|---|---|---|
| VPC‑A (bastion‑vpc) | Secure administrative access | 192.168.0.0/16 |
| VPC‑B (app‑vpc) | Highly available, private application layer | 172.32.0.0/16 |
This separation ensures that administrative access is decoupled from the application environment.
Subnet Layout
Bastion VPC (public only)
| Subnet | CIDR | AZ | Type |
|---|---|---|---|
| Public Subnet | 192.168.1.0/24 | us-east-1a | Public |
Application VPC
| Subnet | CIDR | AZ | Type |
|---|---|---|---|
| Public Subnet | 172.32.1.0/24 | us-east-1a | Public |
| Private Subnet A | 172.32.2.0/24 | us-east-1a | Private |
| Private Subnet B | 172.32.3.0/24 | us-east-1b | Private |
Private subnets across multiple AZs provide high availability.
Internet Connectivity
- Internet Gateways – Create an IGW for each VPC and attach it to the respective VPC.
- Public route tables – Add a default route
0.0.0.0/0 → Internet Gateway.
NAT for Private Subnets
- Allocate an Elastic IP.
- Create a NAT Gateway in the public subnet of the Application VPC.
- Update the private route tables:
0.0.0.0/0 → NAT Gateway.
Transit Gateway
-
Create a Transit Gateway.
-
Attach both VPCs (Bastion and Application) to the TGW.
-
In each VPC’s route table, add a route:
- Destination: CIDR of the other VPC
- Target: Transit Gateway
This eliminates the need for VPC peering and scales better.
Security Groups
| Security Group | Inbound Rules | Outbound Rules |
|---|---|---|
| Bastion SG | SSH (22) from your IP | All traffic |
| Application SG | SSH (22) from Bastion SG HTTP (80) from Load Balancer | All traffic |
These rules enforce a least‑privilege network posture.
Bastion Host
Launch an EC2 instance in the Bastion VPC public subnet with:
- Instance type:
t3.micro - Security group: Bastion SG
- Elastic IP attached
This host becomes the sole SSH entry point into the environment.
Golden AMI (Reusable Image)
#!/usr/bin/env bash
sudo apt update -y
sudo apt install -y apache2 git awscli amazon-cloudwatch-agent
sudo systemctl start apache2
sudo systemctl enable apache2
Installed components
- Apache
- Git
- AWS CLI
- CloudWatch Agent
- SSM Agent (pre‑installed on Amazon Linux 2)
Stop the instance and create an AMI named golden-ami.
Observability
- CloudWatch Log Group –
/vpc/flowlogs - Enable VPC Flow Logs for both VPCs, targeting CloudWatch with separate log streams per VPC.
IAM Role for EC2
Create an IAM role with:
- Trusted entity: EC2
- Managed policy:
AmazonSSMManagedInstanceCore - Custom inline policy: read‑only access to a specific S3 bucket (restricted bucket access)
Attach this role to all EC2 instances to enable secure instance management via Session Manager without SSH keys.
S3 Bucket for Configuration
- Create a bucket (e.g.,
my-app-config-bucket). - Enable default encryption.
- Block all public access.
- Store application configuration files here.
Application Deployment
Launch Template / User Data
#!/usr/bin/env bash
sudo apt install -y git
git clone /var/www/html
systemctl restart apache2
Use the Golden AMI with:
- Instance type:
t3.micro - Security group: Application SG
- IAM role: the role created above
Auto Scaling Group
| Parameter | Value |
|---|---|
| Min size | 2 |
| Desired capacity | 2 |
| Max size | 4 |
| Subnets | Private subnet (us-east-1a) & Private subnet (us-east-1b) |
| Target type | instance |
| Health check | Enabled (EC2) |
| Instance protection | Optional (to avoid termination during deployments) |
Network Load Balancer
- Scheme: Internet‑facing
- Listeners: TCP
:80→ target group (port 80) - Target type: Instance
- Subnets: Public subnets (
172.32.1.0/24in each AZ)
DNS
Create a Route 53 record:
- Type: CNAME
- Name:
app.example.com - Value: NLB DNS name
Validation
- SSH into the Bastion host.
- From the Bastion, SSH into a private EC2 instance (or use Session Manager).
- Open a browser and navigate to
http://app.example.com.
You should see the application loading successfully.
Expected Results
- Application is reachable via the custom domain.
- Auto Scaling responds to traffic spikes.
- VPC Flow Logs and application logs appear in CloudWatch.
Lessons Learned
- Secure AWS network design using a Transit Gateway vs. VPC peering.
- Bastion‑based access patterns for hardened environments.
- Deploying auto‑scaling workloads in private subnets.
- Implementing observability and production‑ready logging.