DEV Community

Cover image for Deploying a Highly Available AWS Application with Bastion Access, Transit Gateway, and Auto Scaling.
Miracle Olorunsola
Miracle Olorunsola

Posted on

Deploying a Highly Available AWS Application with Bastion Access, Transit Gateway, and Auto Scaling.

Introduction

In this project, I built a secure, highly available AWS architecture that mirrors real-world production environments.
The goal was to practice network segmentation, private connectivity, high availability, and observability using native AWS services.

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

Architecture Overview

What We Are Deploying

  • Two VPCs with isolated responsibilities
  • Transit Gateway for private communication
  • Public and private subnets across multiple AZs
  • Bastion host for secure access
  • Auto Scaling Group behind a Network Load Balancer
  • Centralized logging and DNS routing

VPC Design

  • VPC-A (Bastion / Management): Secure administrative access
  • VPC-B (Application): Highly available, private application layer

Prerequisites

Before starting, ensure you have the following in place:

  • AWS account
  • IAM user with administrator access
  • AWS CLI installed and configured
  • EC2 key pair created
  • A registered domain managed in Route53

Step 1: Create the VPCs

We start by isolating responsibilities using two VPCs.

Bastion VPC

  • CIDR: 192.168.0.0/16
  • Name: bastion-vpc

Application VPC

  • CIDR: 172.32.0.0/16
  • Name: app-vpc

This separation ensures administrative access is decoupled from the application environment.


Step 2: Create Subnets

Bastion VPC

Subnet CIDR AZ Type
Public 192.168.1.0/24 us-east-1a Public

Application VPC

Subnet CIDR AZ Type
Public 172.32.1.0/24 us-east-1a Public
Private 172.32.2.0/24 us-east-1a Private
Private 172.32.3.0/24 us-east-1b Private

Private subnets across multiple AZs provide high availability.


Step 3: Internet Gateways

Each VPC requires an Internet Gateway for public connectivity.

  • Create an IGW for both VPCs
  • Attach each IGW to its respective VPC
  • Update public route tables:

    • 0.0.0.0/0 → Internet Gateway

Step 4: NAT Gateway

Private instances still need outbound internet access.

  • Allocate an Elastic IP
  • Create a NAT Gateway in the public subnet of the Application VPC
  • Update private route tables:

    • 0.0.0.0/0 → NAT Gateway

Step 5: Transit Gateway

To enable private VPC-to-VPC communication, we introduce a Transit Gateway.

  • Create a Transit Gateway
  • Attach:

    • Bastion VPC
    • Application VPC
  • Update route tables in both VPCs:

    • Destination: Other VPC CIDR
    • Target: Transit Gateway

This eliminates the need for VPC peering and scales better.


Step 6: Security Groups

Bastion Security Group

  • Allow SSH (22) from your IP
  • Outbound: All traffic

Application Security Group

  • Allow SSH (22) from Bastion SG
  • Allow HTTP (80) from Load Balancer
  • Outbound: All traffic

This enforces least-privilege network access.


Step 7: Bastion Host

  • Launch EC2 in the Bastion VPC public subnet
  • Attach:

    • Bastion security group
    • Elastic IP
  • Instance type: t3.micro

This host becomes the only SSH entry point into the environment.


Step 8: Create a Golden AMI

We build a reusable image for consistency and scaling.

Instance Setup

sudo apt update -y
sudo apt install apache2 git awscli amazon-cloudwatch-agent -y
sudo systemctl start apache2
sudo systemctl enable apache2 

Enter fullscreen mode Exit fullscreen mode

Installed components:

  • Apache
  • Git
  • AWS CLI
  • CloudWatch Agent
  • SSM Agent

Stop the instance and create an AMI named golden-ami.


Step 9: CloudWatch Logs and VPC Flow Logs

Observability is critical in production systems.

  • Create CloudWatch Log Group: /vpc/flowlogs
  • Enable VPC Flow Logs for both VPCs
  • Destination: CloudWatch
  • Separate log streams per VPC

Step 10: IAM Role for EC2

Create an IAM role with:

  • Trusted entity: EC2
  • Policies:

    • AmazonSSMManagedInstanceCore
    • Custom S3 read-only policy (restricted bucket access)

This enables secure instance management without SSH keys.


Step 11: S3 Bucket for App Configuration

  • Create an S3 bucket
  • Enable encryption
  • Block public access
  • Store application configuration files

Step 12: Launch Configuration

Use the Golden AMI and configure:

  • Instance type: t3.micro
  • Security group: Application SG
  • IAM role attached

User Data

#!/bin/bash
sudo apt install git -y
git clone <repo-url> /var/www/html
systemctl restart apache2
Enter fullscreen mode Exit fullscreen mode

Step 13: Auto Scaling Group

  • Min: 2
  • Desired: 2
  • Max: 4
  • Subnets:

    • Private subnet (us-east-1a)
    • Private subnet (us-east-1b)

This ensures fault tolerance and scalability.


Step 14: Target Group

  • Target type: Instance
  • Protocol: TCP
  • Port: 80
  • Health checks enabled

Step 15: Network Load Balancer

  • Internet-facing
  • Subnets: Public subnets
  • Listener: TCP :80
  • Forward traffic to ASG target group

Step 16: Route53 DNS

Create a DNS record:

  • Type: CNAME
  • Name: app.example.com
  • Value: NLB DNS name

Step 17: Validation

Confirm everything works as expected:

  • SSH into Bastion Host
  • SSH into private EC2 via Bastion
  • Access EC2 via Session Manager
  • Open browser: http://app.example.com

Expected Results

  • Application loads successfully
  • Auto Scaling responds to traffic
  • Logs visible in CloudWatch

Final Thoughts

This project helped me deeply understand:

  • Secure AWS network design
  • Transit Gateway vs VPC peering
  • Bastion-based access patterns
  • Auto scaling in private subnets
  • Observability and production readiness

Top comments (0)