236 lines
9.3 KiB
Markdown
236 lines
9.3 KiB
Markdown
# Notes App — AWS Cloud Deployment
|
||
|
||
## What the application does
|
||
|
||
A **Notes** web application where users can create, view, and delete short text notes via a web browser. Notes are stored persistently in a PostgreSQL database. The app is publicly accessible over HTTPS at the configured domain.
|
||
|
||
---
|
||
|
||
## Cloud services and architecture
|
||
|
||
**Cloud provider:** Amazon Web Services (AWS), region `eu-central-1` (Frankfurt)
|
||
|
||
| Service | Purpose |
|
||
|---|---|
|
||
| **Amazon ECS Fargate** | Runs the frontend and backend containers serverlessly. Automatically restarts containers on failure. |
|
||
| **Amazon RDS (PostgreSQL 16)** | Managed relational database. Provides persistent storage, automated backups (7-day retention). |
|
||
| **Application Load Balancer (ALB)** | Receives all internet traffic. Routes `/api/*` to the backend, everything else to the frontend. Redirects HTTP → HTTPS. |
|
||
| **AWS Certificate Manager (ACM)** | Issues and renews a free TLS/HTTPS certificate for the domain. |
|
||
| **Amazon ECR** | Private Docker image registry. Stores the backend and frontend images. |
|
||
| **AWS Secrets Manager** | Stores the database password securely. Never stored in code or Git. |
|
||
| **Amazon CloudWatch Logs** | Collects access logs and application logs from both containers. |
|
||
| **Amazon VPC + Security Groups** | Network isolation. ALB SG allows 80/443 from internet. ECS SG allows traffic only from ALB. RDS SG allows 5432 only from ECS. |
|
||
|
||
### Architecture
|
||
|
||
```
|
||
Browser (HTTPS)
|
||
│
|
||
▼
|
||
Application Load Balancer (port 443, ACM certificate, HTTP→HTTPS redirect)
|
||
│
|
||
├── /api/* ──▶ ECS Fargate: Backend (Flask, port 5000)
|
||
│ │
|
||
│ ▼
|
||
│ RDS PostgreSQL (port 5432, private subnet)
|
||
│
|
||
└── /* ──▶ ECS Fargate: Frontend (Nginx, port 80)
|
||
```
|
||
|
||
The browser loads the static frontend from the ALB. All API calls (`/api/*`) are made by the browser directly to the ALB, which forwards them to the backend. The frontend container serves only static files — it does not proxy to the backend. The backend connects to RDS using the `DATABASE_URL` environment variable.
|
||
|
||
---
|
||
|
||
## Cost analysis — 1,000 users/day, 50 GB database
|
||
|
||
Estimated for **eu-central-1 (Frankfurt)**, 1 year of operation.
|
||
|
||
| Resource | Specification | Unit price | Monthly | Annual |
|
||
|---|---|---|---|---|
|
||
| ECS Fargate — Backend | 0.25 vCPU, 0.5 GB RAM, 730 h/mo | $0.04048/vCPU-h + $0.004445/GB-h | ~$8.30 | ~$99.60 |
|
||
| ECS Fargate — Frontend | 0.25 vCPU, 0.5 GB RAM, 730 h/mo | same | ~$8.30 | ~$99.60 |
|
||
| RDS db.t3.micro | PostgreSQL, 1 vCPU, 1 GB RAM, 730 h/mo | ~$0.022/h | ~$16.10 | ~$193.20 |
|
||
| RDS Storage | 50 GB gp2 | $0.138/GB-mo | ~$6.90 | ~$82.80 |
|
||
| RDS Automated Backups | 50 GB (free up to DB size) | $0.00 | $0.00 | $0.00 |
|
||
| ALB | 1 ALB + ~1,000 req/day (~30k/mo) | $0.0252/h + LCU | ~$18.50 | ~$222.00 |
|
||
| ECR | ~500 MB images stored | $0.10/GB-mo | ~$0.05 | ~$0.60 |
|
||
| CloudWatch Logs | ~5 GB/mo ingestion | $0.57/GB | ~$2.85 | ~$34.20 |
|
||
| Data Transfer Out | ~10 GB/mo (1,000 users × ~330 KB) | $0.09/GB | ~$0.90 | ~$10.80 |
|
||
| Secrets Manager | 1 secret | $0.40/secret/mo | $0.40 | $4.80 |
|
||
| **Total** | | | **~$62.30** | **~$747.60** |
|
||
|
||
> Prices are estimates based on AWS public pricing (2025). Actual costs depend on traffic patterns. Use the [AWS Pricing Calculator](https://calculator.aws) for exact quotes.
|
||
|
||
---
|
||
|
||
## Files and their content
|
||
|
||
```
|
||
sk1/
|
||
├── prepare-app.sh # Creates all AWS resources and deploys the app
|
||
├── remove-app.sh # Tears down all AWS resources
|
||
├── backup.sh # Creates a manual RDS snapshot
|
||
├── .env.example # Template for required environment variables
|
||
├── .gitignore # Excludes .env from Git
|
||
├── backend/
|
||
│ ├── app.py # Flask REST API: GET/POST/DELETE /api/notes, /health
|
||
│ ├── requirements.txt # Python dependencies: flask, psycopg2-binary
|
||
│ └── Dockerfile # python:3.12-slim, runs app.py on port 5000
|
||
├── frontend/
|
||
│ ├── index.html # Single-page app: dark UI, fetch() calls to /api/
|
||
│ ├── nginx.conf # Nginx: serves index.html for all paths
|
||
│ └── Dockerfile # nginx:1.27-alpine, serves static files
|
||
└── db/
|
||
└── init.sql # Creates notes table, inserts sample rows
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration description
|
||
|
||
All secrets and environment-specific values are passed via environment variables, never hardcoded in source files:
|
||
|
||
- **`DATABASE_URL`** — injected into the backend ECS task definition at deploy time. Constructed from `DB_USERNAME`, `DB_PASSWORD`, `DB_HOST`, `DB_NAME`.
|
||
- **`DB_PASSWORD`** — stored in AWS Secrets Manager under `notes-app/db-password`. Set via `.env` before running the script.
|
||
- **`DOMAIN_NAME`** — the public domain for the app (e.g. `notes.example.com`). Used to request the ACM certificate and configure the ALB.
|
||
- **`AWS_REGION`** / **`AWS_ACCOUNT_ID`** — target AWS account and region.
|
||
|
||
The `.env` file is listed in `.gitignore` and must never be committed to Git.
|
||
|
||
---
|
||
|
||
## How to run and use the application
|
||
|
||
### Prerequisites
|
||
|
||
- AWS CLI installed and configured (`aws configure`) with permissions for: ECS, ECR, RDS, ELB, ACM, IAM, EC2, Secrets Manager, CloudWatch Logs
|
||
- Docker installed and running
|
||
- A domain name you control (to add DNS CNAME records)
|
||
|
||
### Deploy
|
||
|
||
```bash
|
||
cd sk1
|
||
cp .env.example .env
|
||
# Edit .env — fill in AWS_REGION, AWS_ACCOUNT_ID, DOMAIN_NAME, DB_PASSWORD
|
||
source .env
|
||
chmod +x prepare-app.sh remove-app.sh
|
||
./prepare-app.sh
|
||
```
|
||
|
||
During deployment the script will print two DNS records to add:
|
||
1. A CNAME for ACM certificate validation
|
||
2. A CNAME pointing your domain to the ALB
|
||
|
||
Once DNS propagates and the certificate is validated, open **https://your-domain.com** in a browser.
|
||
|
||
### Use the app
|
||
|
||
- Type a note in the text box and click **+ Add Note**
|
||
- Notes appear below, newest first
|
||
- Click **✕ Delete** to remove a note
|
||
|
||
### Remove everything
|
||
|
||
```bash
|
||
source .env
|
||
./remove-app.sh
|
||
```
|
||
|
||
> The ACM certificate is not deleted automatically. Delete it manually if no longer needed:
|
||
> `aws acm delete-certificate --certificate-arn <ARN> --region $AWS_REGION`
|
||
|
||
---
|
||
|
||
## How to perform a data backup
|
||
|
||
RDS automated backups are enabled with a **7-day retention period** and run automatically every day.
|
||
|
||
**Manual snapshot using the provided script:**
|
||
```bash
|
||
source .env
|
||
./backup.sh
|
||
```
|
||
|
||
This creates a timestamped RDS snapshot (e.g. `notes-app-manual-20260520-120000`) and waits for it to complete.
|
||
|
||
**Restore from snapshot:**
|
||
```bash
|
||
aws rds restore-db-instance-from-db-snapshot \
|
||
--db-instance-identifier notes-app-db-restored \
|
||
--db-snapshot-identifier <snapshot-id> \
|
||
--region $AWS_REGION
|
||
```
|
||
|
||
**Export data as SQL dump** (requires `psql` and RDS publicly accessible or VPN):
|
||
```bash
|
||
PGPASSWORD=$DB_PASSWORD pg_dump \
|
||
-h <RDS_ENDPOINT> -U $DB_USERNAME -d $DB_NAME \
|
||
> backup_$(date +%Y%m%d).sql
|
||
```
|
||
|
||
---
|
||
|
||
## How to view access logs from the internet
|
||
|
||
Container logs (including Nginx access logs) are streamed to **Amazon CloudWatch Logs**.
|
||
|
||
**Live tail — frontend (Nginx access log):**
|
||
```bash
|
||
aws logs tail /ecs/notes-app/frontend --follow --region $AWS_REGION
|
||
```
|
||
|
||
**Live tail — backend:**
|
||
```bash
|
||
aws logs tail /ecs/notes-app/backend --follow --region $AWS_REGION
|
||
```
|
||
|
||
**Query last 100 log events:**
|
||
```bash
|
||
aws logs get-log-events \
|
||
--log-group-name /ecs/notes-app/frontend \
|
||
--log-stream-name $(aws logs describe-log-streams \
|
||
--log-group-name /ecs/notes-app/frontend \
|
||
--order-by LastEventTime --descending \
|
||
--query "logStreams[0].logStreamName" --output text --region $AWS_REGION) \
|
||
--limit 100 \
|
||
--region $AWS_REGION
|
||
```
|
||
|
||
---
|
||
|
||
## Conditions for running prepare-app.sh and remove-app.sh
|
||
|
||
**prepare-app.sh:**
|
||
- AWS CLI must be installed and configured with credentials that have permissions for: ECS, ECR, RDS, ELB, ACM, IAM, EC2, Secrets Manager, CloudWatch Logs
|
||
- Docker must be running locally
|
||
- `.env` must exist with all required variables set (`AWS_REGION`, `AWS_ACCOUNT_ID`, `DOMAIN_NAME`, `DB_PASSWORD`)
|
||
- The domain in `DOMAIN_NAME` must be one you can add DNS CNAME records to
|
||
- Run from the `sk1/` directory: `source .env && ./prepare-app.sh`
|
||
- The script is idempotent — safe to run multiple times
|
||
|
||
**remove-app.sh:**
|
||
- Same AWS CLI and `.env` requirements as above
|
||
- All deletions are idempotent — safe to run multiple times
|
||
- Run from the `sk1/` directory: `source .env && ./remove-app.sh`
|
||
|
||
**backup.sh:**
|
||
- Same AWS CLI and `.env` requirements as above (`AWS_REGION` is sufficient)
|
||
- RDS instance `notes-app-db` must be running
|
||
- Run from the `sk1/` directory: `source .env && ./backup.sh`
|
||
|
||
---
|
||
|
||
## Use of artificial intelligence
|
||
|
||
This project was developed with the assistance of **Kiro AI** (`kiro-cli chat` agent, Claude Sonnet model).
|
||
|
||
| What | How AI was used |
|
||
|---|---|
|
||
| `prepare-app.sh`, `remove-app.sh`, `backup.sh` | Generated by AI, reviewed and verified by the author against AWS documentation |
|
||
| `README.md` | Generated by AI based on assignment requirements, reviewed and corrected by the author |
|
||
| `backend/app.py`, `frontend/index.html` | Originally written for a previous assignment, adapted with AI assistance |
|
||
| `nginx.conf`, `Dockerfiles`, `init.sql` | Generated by AI, reviewed by the author |
|
||
|
||
The AI was used as a coding assistant. All generated content was reviewed, understood, and verified by the author before submission.
|