CloudDeploy is a small production-inspired FastAPI deployment project built to understand how an application moves from a Git repository to a running service on AWS.
The project deliberately avoids unnecessary enterprise tooling. Instead, it focuses on understanding the fundamentals end-to-end:
networking → Linux → application deployment → database → CI/CD → observability → infrastructure as code
- FastAPI + Uvicorn application deployment
- Amazon EC2 running Amazon Linux 2023
- Nginx as a reverse proxy
systemdfor application process management- PostgreSQL on Amazon RDS
- VPC networking with public and private subnets
- Security groups controlling application/database access
- IAM roles and AWS Systems Manager
- GitHub Actions CI/CD
- GitHub Actions OIDC authentication to AWS
- CloudWatch logs, metrics, and alarms
- Alembic database migrations
- Terraform infrastructure as code
- Remote Terraform state in Amazon S3
The application runs on an EC2 instance inside a public subnet.
Nginx is the public-facing web server and forwards requests to FastAPI/Uvicorn, which listens only on the local machine.
FastAPI connects to PostgreSQL running on Amazon RDS inside private subnets.
RDS is not publicly accessible.
Internet
│
▼
Nginx :80
│
▼
FastAPI / Uvicorn
127.0.0.1:8000
│
│ TCP 5432
▼
Amazon RDS
PostgreSQL
Private
VPC
10.0.0.0/16
│
├── Public Subnet
│ 10.0.1.0/24
│ ap-south-1a
│ │
│ └── EC2
│ ├── Nginx
│ └── FastAPI
│
└── Private Subnets
├── 10.0.2.0/24
│ ap-south-1a
│
└── 10.0.3.0/24
ap-south-1b
│
└── RDS PostgreSQL
The architecture intentionally does not use a NAT Gateway because the current private-subnet workload does not require outbound internet access.
The application runs on EC2 as a systemd service.
The request path is:
Client
↓
Nginx
↓
FastAPI / Uvicorn
↓
PostgreSQL
Uvicorn listens on:
127.0.0.1:8000
This keeps the application server itself from being directly exposed to the internet.
Nginx handles public HTTP traffic and acts as the reverse proxy.
Production deployment is handled by GitHub Actions.
A change merged into main first passes CI. Production deployment only starts after the CI workflow succeeds.
Git push / merge
│
▼
GitHub Actions CI
│
┌─────────────┴─────────────┐
│ │
PostgreSQL service Code validation
│ │
├── Alembic ├── Ruff
├── pytest ├── formatting
└── migrations └── dependencies
│
▼
Success
│
▼
Production Deploy
│
GitHub OIDC
│
▼
AWS IAM Role
│
▼
AWS Systems Manager
│
▼
EC2
│
checkout exact SHA
│
uv sync --locked
│
alembic upgrade head
│
restart systemd
│
health check
The deployment uses AWS Systems Manager instead of SSH-based deployment.
GitHub Actions authenticates to AWS through OIDC, so long-lived AWS access keys are not stored in GitHub.
The deployment also uses the exact commit SHA that passed CI rather than simply deploying whatever happens to be at the tip of main.
For this project, SSM provides a simple deployment mechanism without introducing another deployment platform.
The project intentionally does not use:
- AWS CodeDeploy
- AWS CodePipeline
- Docker
- Kubernetes
- self-hosted GitHub runners
The goal is to understand the underlying deployment process rather than hide it behind additional services.
The AWS infrastructure was built and tested manually first.
Terraform was then introduced to bring the existing infrastructure under Infrastructure as Code.
The existing resources were imported rather than destroyed and recreated.
Existing AWS infrastructure
│
▼
Terraform resource definitions
│
▼
Import existing resources
│
▼
Terraform state
│
▼
terraform plan
│
▼
No changes
The production Terraform state is stored remotely in Amazon S3.
infrastructure/
└── terraform/
├── bootstrap/
├── environments/
│ └── production/
└── modules/
├── networking/
├── security/
├── iam/
├── compute/
├── database/
└── monitoring/
The modules correspond to meaningful parts of the CloudDeploy architecture rather than abstracting every individual AWS resource.
The current Terraform state manages the core CloudDeploy infrastructure, including:
- VPC
- public and private subnets
- Internet Gateway
- public routing
- EC2
- RDS
- RDS subnet group
- RDS security group and rules
- EC2 IAM role and instance profile
- CloudWatch log groups
- CloudWatch alarms
The current production configuration successfully passes:
terraform fmt
terraform validate
terraform plan
with:
No changes. Your infrastructure matches the configuration.
Not every AWS resource in the account is intentionally managed by Terraform. The scope is limited to infrastructure that belongs to the CloudDeploy environment.
CloudWatch provides basic operational visibility.
The environment collects:
- CPU metrics
- memory metrics
- disk metrics
- Nginx access logs
- Nginx error logs
- EC2 status checks
CloudWatch alarms are configured for basic infrastructure health monitoring.
FastAPI/Uvicorn application logs remain available through the systemd journal on EC2.
Several security boundaries are intentionally maintained:
- RDS is private and not publicly accessible.
- PostgreSQL access is restricted to the application security group.
- FastAPI listens only on
127.0.0.1. - Nginx is the public-facing application entry point.
- EC2 uses an IAM instance role rather than static AWS credentials.
- GitHub Actions uses OIDC rather than long-lived AWS access keys.
- Production deployment is performed through AWS Systems Manager.
- The EC2 security group restricts SSH access rather than exposing port 22 globally.
The project favors simple, understandable security controls over adding infrastructure that is unnecessary for the application's scale.
Current deployment region:
ap-south-1
Core resources:
| Resource | Configuration |
|---|---|
| VPC | 10.0.0.0/16 |
| Public subnet | 10.0.1.0/24 |
| Private subnet | 10.0.2.0/24 |
| Private subnet | 10.0.3.0/24 |
| EC2 | t3.micro |
| Database | PostgreSQL on RDS |
| Web server | Nginx |
| Application server | FastAPI / Uvicorn |
| Process manager | systemd |
| Infrastructure | Terraform |
| CI/CD | GitHub Actions |
| AWS authentication | GitHub OIDC |
| Remote execution | AWS Systems Manager |
| Monitoring | CloudWatch |
clouddeploy-fastapi/
│
├── app/
│ └── ...
│
├── tests/
│ └── ...
│
├── infrastructure/
│ └── terraform/
│ ├── bootstrap/
│ ├── environments/
│ │ └── production/
│ └── modules/
│
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
│
├── docs/
│ └── images/
│
├── alembic/
│ └── ...
│
├── pyproject.toml
├── uv.lock
└── README.md
The infrastructure was intentionally built manually before introducing Terraform.
This made it possible to understand the AWS resources and networking behavior before representing them as code.
Terraform was then used to manage the existing environment rather than hiding the initial infrastructure decisions behind automation.
The project uses a single EC2 application server and a single RDS PostgreSQL instance.
It intentionally does not introduce:
- NAT Gateway
- load balancer
- RDS Proxy
- Multi-AZ application infrastructure
- auto scaling
- Kubernetes
- containers
These services can be useful at larger scales, but they would add complexity without providing meaningful value for the current project.
GitHub Actions authenticates to AWS through OIDC and deploys through Systems Manager.
This avoids storing long-lived AWS credentials and avoids maintaining a separate SSH-based deployment mechanism.
Terraform manages the infrastructure that directly belongs to CloudDeploy.
The goal is not to make every AWS resource in the account Terraform-managed.
CloudDeploy is currently deployed and working on AWS.
- Manual AWS infrastructure
- FastAPI deployment on EC2
- PostgreSQL on RDS
- Nginx reverse proxy
- systemd process management
- IAM roles and Systems Manager
- GitHub Actions CI
- GitHub Actions OIDC
- Automated production deployment
- CloudWatch monitoring and logging
- Terraform modules
- Terraform remote state
- Import existing AWS infrastructure into Terraform
- Terraform reconciliation with
No changes
The core deployment, CI/CD pipeline, monitoring, and Terraform infrastructure are working together as a single deployment system.
The project is now primarily in the documentation and final portfolio-polish phase, rather than adding more infrastructure.
CloudDeploy is intentionally small.
The objective was not to build the most complicated AWS architecture possible. The objective was to understand what actually happens between:
Git repository
↓
CI
↓
AWS authentication
↓
Deployment
↓
Linux process
↓
Web server
↓
Application
↓
Database
↓
Monitoring
↓
Infrastructure as Code
The project started with manually built infrastructure, moved through a working production deployment, and finally brought the infrastructure under Terraform management.
The result is a small but complete example of deploying and operating a backend application on AWS.
