
<aside> ✅
Outcome: my portfolio site “app.bolarinwadaniel.online” is live over HTTPS on three private web servers in an Auto Scaling group across two Availability Zones, all serving one shared copy of the site from Amazon EFS. Datadog agents on every server alert Slack when CPU passes 75%.
</aside>
The design goals were:
| Component | Name in my account | Purpose |
|---|---|---|
| Region | us-east-1 (N. Virginia) | Hosts everything |
| Load balancer | Application Load Balancer + target group | Public HTTPS entry point (443) with an ACM certificate, health checks |
| Auto Scaling group | webserver-auto (desired 3, limits 3 to 12) | Keeps three healthy web servers running |
| Launch template | project-temp, version 1 | Boots each web server fully configured via user data |
| Web servers | 3 x t3.small, private IPs only | Run nginx and serve the site on port 80 |
| Bastion | Jumper-Server-01 (t3.small, Ubuntu 24.04) | SSH entry point, uploads site files to EFS |
| NAT gateway | Public subnet | Outbound internet for the private web servers |
| Shared storage | Amazon EFS, Regional, NFS 4.1 | One copy of the site for every server |
| Monitoring | Datadog Agent 7 + Slack | Agents on all 4 servers, CPU alert at 75% to Slack |
| DNS | Route 53 hosted zone, alias record to the ALB | Points app.bolarinwadaniel.online at the load balancer |
| Network logging | VPC Flow Logs on portfolio-vpc | Records accepted and rejected traffic for troubleshooting and security review |
Visitors resolve my domain through Route 53 and reach the load balancer over HTTPS, the load balancer forwards to the web servers, and every web server reads the site from the same EFS file system.
flowchart TB
V["Visitors"] -->|"DNS lookup"| R53["Route 53<br>app.bolarinwadaniel.online"]
R53 -->|"HTTPS :443"| ALB
A["Admin (Mac)"] -->|"SSH :22"| B
subgraph VPC["VPC 192.168.0.0/16 · us-east-1 · Flow Logs enabled"]
subgraph PUB["Public subnets"]
ALB["Application Load Balancer<br>ACM certificate"]
NAT["NAT gateway"]
B["Jumper-Server-01 (bastion)<br>Datadog agent"]
end
subgraph ASG["Auto Scaling group webserver-auto"]
subgraph AZA["us-east-1a · private subnet"]
W1["Web server 1<br>nginx · Datadog agent"]
W2["Web server 2<br>nginx · Datadog agent"]
end
subgraph AZB["us-east-1b · private subnet"]
W3["Web server 3<br>nginx · Datadog agent"]
end
end
EFS[("Amazon EFS<br>shared site files")]
end
ALB -->|"HTTP :80"| W1 & W2 & W3
B -->|"SSH :22"| W1 & W2 & W3
W1 & W2 & W3 -.->|"outbound"| NAT
W1 & W2 & W3 -->|"NFS 2049"| EFS
B -->|"writes site files"| EFS
W1 & W2 & W3 & B -->|"metrics"| DD["Datadog"]
DD -->|"CPU over 75%"| S["Slack · team devices"]
The bastion is the only SSH entry point and also writes the site files to EFS, so an upload there is live on every server at once.
The build runs in seven stages, and the order matters: EFS must be reachable before any web server boots, or the servers come up serving an empty folder.
public-sg for the bastion and private-sg for the web servers. The intent is that web servers accept SSH only from the bastion and HTTP only from the load balancer.