A complete 3-Tier web application architecture containerized with Docker Compose, plus a one-command automated deployment script (deploy.sh) for migrating to a production Linux cloud server (GCP, AWS, Azure, DigitalOcean).
Portfolio goal: Demonstrate cloud migration skills, Docker networking, multi-tier architecture design, and CI/CD-ready deployment automation.
Internet
│
▼
┌─────────────────────────────────────────┐
│ Tier 1: Nginx (Reverse Proxy) │
│ Port 80 — serves static files + │
│ proxies /api/* → Flask │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Tier 2: Flask Application Server │
│ Port 5000 (internal) — REST API │
│ Connects to PostgreSQL via SQLAlchemy │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Tier 3: PostgreSQL Database │
│ Port 5432 (internal only) │
│ Persistent volume — survives restarts │
└─────────────────────────────────────────┘
Local development:
Cloud deployment:
- A Linux VPS (Ubuntu 20.04+) with SSH access
- SSH key pair configured
# 1. Copy environment template
cp .env.example .env
# 2. Start all 3 tiers
docker compose up -d --build
# 3. Access the application
# Frontend: http://localhost
# API Health: http://localhost/api/health
# API Items: http://localhost/api/itemschmod +x deploy.sh
./deploy.sh <YOUR_SERVER_IP> <SSH_USER>
# Example:
./deploy.sh 34.126.120.50 ubuntuWhat deploy.sh does automatically:
rsync— copies project files to the remote server- Installs Docker if not already present
- Runs
docker compose up -d --buildon the server - Performs HTTP health check on
/api/health - Reports success or failure with the public URL
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Nginx-served frontend landing page |
| GET | /api/health |
Health check — returns {"status": "ok"} |
| GET | /api/items |
List all items from PostgreSQL |
| POST | /api/items |
Create a new item {"name": "..."} |
16_cloud_migration_3tier_app/
├── .github/workflows/ci.yml # Build validation CI
├── app/
│ ├── app.py # Flask API server
│ ├── requirements.txt
│ └── Dockerfile
├── nginx/
│ ├── nginx.conf # Reverse proxy config
│ └── html/
│ └── index.html # Landing page
├── db/
│ └── init.sql # PostgreSQL schema + seed
├── docker-compose.yml # 3-tier orchestration
├── deploy.sh # One-command cloud deploy
├── .env.example # Environment variables template
└── README_VI.md # Vietnamese documentation
cp .env.example .env| Variable | Default | Description |
|---|---|---|
POSTGRES_DB |
appdb |
Database name |
POSTGRES_USER |
appuser |
DB username |
POSTGRES_PASSWORD |
(set in .env) | DB password — never hardcode |
FLASK_ENV |
production |
Flask environment |
- Database credentials are loaded via
.env— never committed to Git - Database port (5432) is not exposed externally — internal Docker network only
- Nginx acts as the only public-facing entry point