← AWS Learning Lab

Lab 04

Three-Tier Architecture

A multi-AZ network with public load balancing, private application instances, and a shared private PostgreSQL database.

VPC · ALB · EC2 · ASG · RDS · Nginx · Python

Lab completed

Build beyond a single public server.

The goal was to understand how a traditional web application is separated into network, application, and data layers—and how traffic, permissions, and availability change when the application spans two Availability Zones.

Instead of exposing EC2 or PostgreSQL directly to the internet, the architecture needed one controlled public entry point. The application instances would remain private, be managed as a group, and query the same private database.

Public at the edge, private behind it.

The internet-facing Application Load Balancer spans public subnets in two Availability Zones. Its target group routes healthy requests to EC2 instances launched from one template and managed by an Auto Scaling Group in private application subnets.

Both instances run Nginx in front of the Python application and connect to the same RDS PostgreSQL database in private database subnets. Security groups define which tier may initiate traffic to the next.

Built the foundation before the application.

  1. 01
    Designed the VPC and subnets

    Created public and private subnet groups across two Availability Zones so public entry points, application compute, and database resources could occupy different network layers.

  2. 02
    Configured internet and outbound routing

    Attached an Internet Gateway for public-subnet connectivity, added a NAT Gateway for private application instances that need outbound access, and associated the appropriate route tables with each subnet.

  3. 03
    Separated traffic with security groups

    Allowed web traffic to the ALB, application traffic from the ALB to EC2, and PostgreSQL traffic from the application tier to RDS. The EC2 and database tiers were not opened directly to the internet.

  4. 04
    Created the database tier

    Provisioned private RDS PostgreSQL as the shared data service used by every application instance.

  5. 05
    Built the application runtime

    Created a small Python database-backed application and placed Nginx in front of it on EC2. The /message page queries PostgreSQL and prints the current EC2 hostname as Served by:.

  6. 06
    Made instances repeatable

    Captured the EC2 configuration in a Launch Template and used an Auto Scaling Group to maintain application instances across the two private subnets.

  7. 07
    Added load balancing and health checks

    Created an internet-facing ALB, target group, listener, and health-check path so requests are routed only to registered healthy application instances.

The hostname made the invisible routing visible.

The application intentionally exposes one harmless diagnostic detail: the hostname of the EC2 instance serving the response. This made it possible to see the load balancer choose between otherwise identical targets.

  • 01
    Target health

    Verified both ASG-managed EC2 instances registered as healthy in the target group and could receive ALB traffic.

  • 02
    Request distribution

    Refreshed /message and watched Served by: alternate between the two EC2 hostnames, demonstrating that the ALB was distributing requests.

  • 03
    Shared state

    Confirmed that responses from either application instance queried the same RDS PostgreSQL database rather than storing data on an individual server.

  • 04
    Network boundaries

    Confirmed the public request enters through the ALB while the application instances and database remain on private addresses behind their security-group rules.

  • 05
    Health-check path

    Verified the target group used application health checks to determine whether each EC2 instance was eligible for new requests.

The hard part is how the layers connect.

  • 01
    Public and private are routing decisions.

    A resource’s subnet, route table, public address, and security-group rules work together. A “private tier” is not created by one checkbox.

  • 02
    Security groups express service relationships.

    The useful question is not simply which ports are open, but which tier is allowed to initiate a specific connection to which other tier.

  • 03
    Load balancing depends on repeatable compute.

    A Launch Template and Auto Scaling Group make the EC2 instances members of one application tier rather than individually configured servers.

  • 04
    Shared data keeps the web tier interchangeable.

    Because both instances use the same RDS database, the ALB can route a request to either server without splitting the application’s source of truth.

  • 05
    Observability can start with a deliberate signal.

    Displaying the serving hostname turned an abstract load-balancing claim into behavior I could repeatedly see and verify.

  • 06
    Multi-AZ design affects every layer.

    Subnets, ALB nodes, application targets, route associations, and database placement must be planned as one topology rather than as isolated resources.