What I built
A complete AWS environment designed to host and deploy a web application securely, while keeping its public, application, and data layers separate.
A production-inspired AWS environment for running a containerized web application securely and consistently.
• Personal Project • GitHub repository ↗
What I built
A complete AWS environment designed to host and deploy a web application securely, while keeping its public, application, and data layers separate.
>_This stack automates the journey from source code to a secure, running cloud environment.
To understand how a complete cloud environment works, not just how to create individual AWS resources.
A working web application environment that can be created automatically, tested, and removed when it is no longer needed.
I designed the architecture and implemented the infrastructure, security boundaries, application packaging, and deployment workflows.
Users can access the website, but they cannot connect directly to the servers or database behind it. Each request follows a controlled path through the application.
Users reach the application through a secure public endpoint.
Cloudflare → public ALB
The frontend is served by private application servers that can scale automatically.
Public ALB → Nginx
API requests are handled separately from the frontend for better isolation.
Nginx → internal ALB → Flask/Gunicorn
Application data is stored in an isolated managed database with no public access.
Flask/Gunicorn → RDS MySQL
The main design choices that make the environment secure, maintainable and repeatable.
Web and application instances have no direct public entry path.
Incoming requests must pass through the load-balancing boundaries instead of reaching EC2 directly.
The public and application-facing traffic paths remain distinct.
A public ALB handles browser traffic while an internal ALB exposes the API only to the web tier, keeping routing and health checks explicit.
Infrastructure concerns have separate, reviewable responsibilities.
Networking, security groups, load balancers, compute, RDS, DNS and CI/CD IAM can change without one tightly coupled configuration.
GitHub Actions uses short-lived AWS sessions instead of stored keys.
OIDC and operation-specific roles reduce the reach of each workflow and remove long-lived secrets.
Release metadata and database credentials remain outside application images.
Image versions and non-secret database metadata live in SSM Parameter Store; the RDS password stays in its AWS-managed secret.
Instance Refresh rolls the selected container release through each tier.
New instances retrieve the intended image tag during startup and must pass load-balancer health checks.
Infrastructure changes and application releases use separate automated workflows.
GitHub Actions connects to AWS through OpenID Connect (OIDC), using short-lived
access and distinct approval gates for each operation.
01
Test the Flask backend and build the Vue frontend.
02
Build immutable backend and frontend Docker images tagged with the Git commit SHA.
03
Push both application images to Amazon ECR.
04
Run the database migration before the new image tags become active.
05
Update SSM image parameters, then refresh the application tier before the web tier.
Every layer accepts traffic only from the layer immediately before it. AWS security groups enforce this path without exposing the application servers or database directly to the internet.
Instances are managed through AWS Systems Manager.
There is no public SSH path or inbound SSH rule.
Database credentials remain in the AWS-managed RDS secret.
The backend role retrieves only the required metadata and secret at runtime.
GitHub Actions authenticates to AWS through OIDC.
Planning, infrastructure changes and application deployment use separate IAM roles.
This estimate models the main resources in the architecture, including four burstable EC2 instances, two Application Load Balancers, two NAT gateways, an ECR repository and a Multi-AZ RDS MySQL database.
Networking is the largest projected cost category, mainly due to NAT gateways and load balancers. The environment is intended to be provisioned temporarily rather than left running continuously.
Directional estimate only. Actual cost depends on runtime, traffic, storage, data transfer, logging and current AWS pricing.
per month
per month
per month
per month
Production-inspired architecture — prioritizes availability and isolation over minimum cost.
Resource-level breakdown derived from the original monthly projection.
| Service | Description | Monthly cost | % of total |
|---|---|---|---|
| Networking | 2 NAT gateways, 2 Application Load Balancers | $128.06 | 66.6% |
| Compute (EC2) | 4 × burstable instances | $35.04 | 18.2% |
| Database (RDS) | RDS MySQL (Multi-AZ) | $29.20 | 15.2% |
| Containers (ECR) | ECR repository storage | $0.10 | 0.1% |
These are deliberate compromises that keep this portfolio demo simple and cost-effective while acknowledging what would change in a longer-lived or production-grade setup.
The ACM certificate, ECR repository, GitHub OIDC provider and some CloudWatch configuration must exist before deployment. Keeping them outside the main stack narrows its focus, but means the environment is not yet reproducible through one command.
I learned to design outbound access for ECR, SSM, CloudWatch and other AWS APIs alongside routing, IAM and instance bootstrapping.
Bootstrapping the backend separately clarified why control-plane resources should outlive short-lived workload resources.
Separate OIDC roles made least privilege more concrete than one shared automation role with planning, apply and deployment access.
Starting an instance is not a completed rollout; replacement capacity must start its containers and pass target-group health checks.
Reduce NAT dependency for SSM, ECR, CloudWatch Logs and S3 while keeping AWS service traffic on the AWS network.
Enable VPC Flow Logs and ALB access logs, then add CloudWatch dashboards and alarms for the existing infrastructure and application signals.
Add development, staging and production configurations with appropriate deletion protection, backups, retention, scaling and approval defaults.
Bootstrap the remaining CloudWatch agent parameters and setup dependencies consistently to remove manual first-deployment steps.
Detailed views of topology, routing, runtime configuration and delivery.