A production-grade URL shortener REST API built for the MLH Production Engineering hackathon. It covers all 4 tracks: Reliability, Scalability, Incident Response, and Documentation.
Who is this for? Engineers who want a reference implementation of a URL shortener that is built to production standards β with horizontal auto-scaling, a full observability stack, zero-downtime deploys, chaos testing, and 91% test coverage β all running on a single DigitalOcean droplet.
Live: http://64.225.10.147 | Docs: http://64.225.10.147/docs/ | Dashboard: http://64.225.10.147:3000
Create shortened URLs that redirect users to their targets. The system scales horizontally with auto-scaling replicas, caches hot reads in Redis with a circuit breaker, and logs everything to a full observability stack (Prometheus, Grafana, Loki, Jaeger) with sub-90-second alert latency. Handles 400+ req/s on a single droplet with 99.9% uptime and zero downtime deploys.
| Grafana β Golden Signals Dashboard | Swagger UI β API Docs |
|---|---|
![]() |
![]() |
k6 Load Test β 500 VUs, 0% error rate

graph TD
Client --> Nginx["Nginx (rate limit + gzip)"]
Nginx -->|"proxy_pass + retry"| AppR1["App Replica 1 (Gunicorn gthread)"]
Nginx -->|"proxy_pass + retry"| AppR2["App Replica 2"]
AppR1 -->|"circuit breaker"| Redis["Redis (LFU, 128MB)"]
AppR2 -->|"circuit breaker"| Redis
AppR1 -->|"pooled conn"| PostgreSQL["PostgreSQL (tuned)"]
AppR2 -->|"pooled conn"| PostgreSQL
Prometheus -->|"scrape /metrics"| AppR1
Prometheus -->|"scrape /metrics"| AppR2
Prometheus --> Alertmanager --> Discord
Alertmanager --> Email
Grafana --> Prometheus
Grafana --> Loki
flowchart TD
A([Push to main / PR to main or dev]) --> B{Event type?}
B -->|PR to main or dev| C[π Lint Job\nRuff check + format]
B -->|Push to main| C
C -->|β Lint fails| FAIL1([β Pipeline blocked])
C -->|β
Lint passes| D{Push to main?}
D -->|No β PR only| END_PR([β
PR checks pass])
D -->|Yes| E[π³ Build Job\nDocker Buildx]
E --> E1[Lowercase image name]
E1 --> E2[Log in to GHCR]
E2 --> E3[Generate Docker meta\nsha / branch / latest tags]
E3 --> E4[Build & push image\nto ghcr.io with GHA cache]
E4 --> F[π‘οΈ Scan Job\nTrivy vulnerability scanner]
F --> F1[Scan for CRITICAL + HIGH CVEs]
F1 --> F2[Upload SARIF to GitHub\nCode Scanning]
E4 --> G[π Deploy Job\nDigitalOcean Droplet]
G --> G1[Install sshpass]
G1 --> G2[SSH into Droplet\nvia DROPLET_IP / USER / PASS]
G2 --> G3[git stash β checkout main\nβ git pull origin main]
G3 --> G4[docker compose up -d\n--build --remove-orphans]
G4 --> G5{Health check loop\n30 retries / 1s}
G5 -->|curl /health β
| G6[Print container status\ndocker compose ps]
G5 -->|All 30 retries fail| FAIL2[docker compose logs\ntail=20 app]
FAIL2 --> FAIL3([β Deploy failed])
G6 --> SUCCESS([β
Deploy complete])
F2 --> SUCCESS
style A fill:#4f46e5,color:#fff
style SUCCESS fill:#16a34a,color:#fff
style FAIL1 fill:#dc2626,color:#fff
style FAIL2 fill:#dc2626,color:#fff
style FAIL3 fill:#dc2626,color:#fff
style END_PR fill:#0891b2,color:#fff
| File | Trigger | Jobs |
|---|---|---|
.github/workflows/ci.yml |
push main, PRs to main/dev |
lint β build β scan |
.github/workflows/deploy.yml |
push main only |
SSH deploy to droplet |
Note: Both workflows fire in parallel on push to
mainβ the deploy does not wait for the vulnerability scan to complete.
- Python 3.13+
uvpackage manager:curl -LsSf https://astral.sh/uv/install.sh | sh- PostgreSQL 16 (or use Docker Compose below)
- Docker & Docker Compose (for full stack)
git clone https://github.com/RETR0-OS/MLH_PE_URL_Shortener.git
cd MLH_PE_URL_Shortener
cp .env.example .env
# Edit .env if you want to customize (usually not needed locally)
docker compose up -d --build
# Wait for services to be healthy (30-60s)
curl http://localhost/health
# β {"status": "ok"}
# Seed sample data
docker compose exec app python scripts/seed.py
# Open dashboards
# - API: http://localhost/docs
# - Grafana: http://localhost:3000 (admin/admin)
# - Prometheus: http://localhost:9090git clone https://github.com/RETR0-OS/MLH_PE_URL_Shortener.git
cd MLH_PE_URL_Shortener
uv sync
# Create database
createdb hackathon_db
# Run the dev server
uv run flask --app run:app run --port 5000
# In another terminal, seed data (optional)
uv run python scripts/seed.py
# Run tests
uv run pytest --cov=app# Create a user
curl -X POST http://localhost/users \
-H "Content-Type: application/json" \
-d '{"username":"alice","email":"alice@example.com"}'
# Create a shortened URL
curl -X POST http://localhost/urls \
-H "Content-Type: application/json" \
-d '{
"user_id": 1,
"original_url": "https://github.com/RETR0-OS/MLH_PE_URL_Shortener",
"title": "URL Shortener Repo"
}'
# Follow the short link (302 redirect)
curl -L http://localhost/urls/abc123/redirectSee docs/DEPLOYMENT.md for:
- Deploying to DigitalOcean droplet
- Zero-downtime rolling updates
- Rollback procedures
- Manual rollback (emergency)
- Production monitoring & SLOs
Automated deploys: Merge to main branch β CI/CD pipeline runs tests β If passing, auto-deploys to production with health check gates.
| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness probe |
| GET | /health/ready |
Readiness probe (DB check) |
| GET | /users |
List users (paginated) |
| POST | /users |
Create user |
| POST | /users/bulk |
Bulk import users (CSV) |
| GET | /users/{id} |
Get user by ID |
| PUT | /users/{id} |
Update user |
| DELETE | /users/{id} |
Delete user |
| GET | /urls |
List URLs (filter by ?user_id=) |
| POST | /urls |
Create shortened URL |
| GET | /urls/{id} |
Get URL by ID |
| PUT | /urls/{id} |
Update URL |
| DELETE | /urls/{id} |
Delete URL |
| GET | /urls/{short_code}/redirect |
Redirect to original URL (302) |
| GET | /events |
List events |
| GET | /events/{id} |
Get event by ID |
| POST | /events |
Create event |
| PUT | /events/{id} |
Update event |
| GET | /metrics |
Prometheus metrics |
Full API specification: docs/openapi.yaml
| Service | Port |
|---|---|
| App (Gunicorn) | 5000 |
| Nginx | 80 |
| PostgreSQL | 5432 |
| Redis | 6379 |
| Prometheus | 9090 |
| Alertmanager | 9093 |
| Grafana | 3000 |
| Loki | 3100 |
Getting Started:
- Architecture Overview β System design, components, data flow
- Quick Setup Guide β Getting running in 2 minutes
- Configuration & Environment Variables β All env vars explained
Deployment & Operations:
- Deployment Guide β Deploy to production, rollback procedures, monitoring
- Capacity Planning β Current bottlenecks, scaling scenarios, growth roadmap
Production:
- API Documentation β Interactive Swagger UI
- Reliability Engineering β 91% test coverage, CI/CD pipeline, chaos testing
- Scalability Engineering β Load testing results, architectural decisions, bottleneck analysis
- [Incident Response](docs/Incident Response/runbooks/INCIDENT-PLAYBOOK.md) β On-call runbook, alert response procedures
Design & Architecture:
- Decision Log β All 19 major technical choices: why Flask, PostgreSQL, Redis, Nginx, Gunicorn, Prometheus, Loki, Grafana, Alertmanager, Docker, GitHub Actions, DigitalOcean, uv, k6, and more
- Incident Response Design Decisions β Monitoring, alerting, observability choices (detailed evidence map)
- Root Cause Analysis Template β Google SRE 5-Whys format for postmortems
Docker Compose fails to start:
# Check logs
docker compose logs
# Common issue: Port already in use
sudo lsof -i :80
# Kill the conflicting process or change port in docker-compose.yml
# Try a full rebuild
docker compose down --volumes
docker compose up -d --buildHealth check fails after starting:
# Wait 30 seconds and try again (migrations might be running)
sleep 30
curl http://localhost/health/ready
# Check app logs
docker compose logs app | tail -50
# If database connection fails:
docker compose exec postgres psql -U postgres -d hackathon_db -c "SELECT 1;"Tests failing locally:
# Make sure services are running
docker compose up -d
# Clear Python cache
find . -type d -name __pycache__ -exec rm -r {} +
# Run tests with verbose output
uv run pytest -v --tb=short
# Run specific test file
uv run pytest tests/test_urls.py -vHigh error rate in production:
- Open Grafana: http://64.225.10.147:3000
- Check "Error Rate" panel
- Look at "Application Logs" panel for error messages
- See [
docs/Incident Response/runbooks/INCIDENT-PLAYBOOK.md](docs/Incident Response/runbooks/INCIDENT-PLAYBOOK.md) for full runbooks
Latency spike:
- Check CPU/memory in Grafana dashboard
- If CPU > 75%, autoscaler should scale up (check
docker compose ps) - If autoscaler is scaling but latency stays high, see capacity planning docs
See docs/DEPLOYMENT.md for more troubleshooting.
users
id SERIAL PK
username TEXT UNIQUE
email TEXT UNIQUE
created_at TIMESTAMP
urls
id SERIAL PK
user_id FK β users.id
short_code TEXT UNIQUE β random 6-char alphanumeric, indexed
original_url TEXT
title TEXT
is_active BOOLEAN
created_at TIMESTAMP
updated_at TIMESTAMP
events
id SERIAL PK
url_id FK β urls.id (nullable)
user_id FK β users.id (nullable)
event_type TEXT β 'redirect', 'created', 'updated'
timestamp TIMESTAMP
details JSONB
9 indexes cover all hot read paths. Events are written asynchronously via ThreadPoolExecutor (fire-and-forget) so they never block the response path.
- Fork the repository and create a branch from
main:git checkout -b feat/your-feature - Run the full test suite before pushing:
uv run pytest --cov=app --cov-fail-under=70 - Open a PR against
dev(notmain). CI runs 177 unit/integration tests, a 500-VU k6 load test, and ruff lint automatically. - All 3 CI checks must be green before merging. The PR template has a checklist β fill it out honestly.
mainis the production branch. Merging tomaintriggers an automatic deploy to the DigitalOcean droplet.
Dev environment setup: Follow Option A (Docker Compose) in Quick Start β it takes 2 minutes and gives you the full stack including Grafana dashboards.
- Runtime: Python 3.13, Flask, Gunicorn (gthread)
- Database: PostgreSQL 16 (tuned), Peewee ORM (pooled connections)
- Cache: Redis 7 (LFU eviction, circuit-breaker fallback)
- Proxy: Nginx (rate limiting, gzip, keepalive, proxy retries)
- Observability: Prometheus, Alertmanager, Grafana, Loki, Jaeger
- CI/CD: GitHub Actions (pytest + coverage gate + load tests)
- Orchestration: Docker Compose (auto-scaling, rolling deploys, health checks)
- Testing: pytest (91% coverage), k6 (load testing)
- Package manager: uv (Python 3.13, fast lockfile-based installs)

