Leaderboard Fantasy is a fantasy golf platform with an origin story that captures the essence of pragmatic software development: it started as a color-coded Excel spreadsheet, graduated to a cloud-native GCP deployment, and then evolved into a lean, self-hosted VPS solution—all while serving hundreds of golf fans.
More importantly, it's a case study in frugal engineering: the discipline of building production-quality software without burning through cloud credits, and knowing when managed services are worth the premium versus when they're just expensive convenience.
Like many great products, Leaderboard Fantasy began with a personal pain point. Managing a fantasy golf pool for friends meant:
The spreadsheet grew unwieldy. Friends of friends wanted in. Other groups heard about it and asked to use it too. That's when the realization hit: this wasn't just a personal problem—fantasy golf fans everywhere were either struggling with spreadsheets or settling for platforms that didn't understand the sport.
The first production version was built on Google Cloud Platform, embracing every managed service available:
It worked beautifully. Auto-scaling, managed SSL, global CDN—the full enterprise experience. But there was a problem: the monthly bill didn't match the usage pattern.
Fantasy golf is seasonal. Traffic spikes during major tournaments, then drops to near-zero between events. Paying for always-on managed services to handle a hobby project's traffic felt increasingly wasteful.
In January 2026, Leaderboard Fantasy moved to a budget VPS provider. The goal: maintain production reliability while dramatically reducing costs.
Monthly cost comparison:
| Component | GCP (Before) | VPS (After) |
|---|---|---|
| Compute | ~$50-80 (App Engine + Cloud Run) | ~$12 (VPS) |
| Database | ~$25-40 (MongoDB Atlas) | $0 (Self-hosted) |
| Redis | ~$15-25 (Memorystore) | $0 (Self-hosted) |
| Total | ~$100-170/month | ~$12/month |
The lesson: managed services are valuable when you're scaling or need guarantees you can't provide yourself. For a hobby project with predictable traffic, they're often overkill.
The current production stack runs entirely on a single VPS, orchestrated through Docker Compose with Cloudflare Tunnel providing secure ingress.
┌────────────────────────────────────────────────────────────────────┐
│ VPS Host │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Cloudflare │◄──── Internet traffic via Cloudflare Edge │
│ │ Tunnel │ (DDoS protection, SSL termination) │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ nginx │────►│ leaderboard-web │────►│ lfs-data │ │
│ │ (Proxy) │ │ (Frontend) │ │ (API) │ │
│ └──────────────┘ └─────────────────┘ └───────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Redis │ │ MongoDB │ │
│ │ (Sessions) │ │ (Database) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ • Named volumes for data persistence │ │
│ │ • Health checks for automatic recovery │ │
│ │ • Isolated Docker network │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
The VPS has no ports exposed to the internet except SSH for management. All web traffic flows through Cloudflare Tunnel:
# Traffic flow
Internet → Cloudflare Edge → Tunnel → nginx → Application Services
The docker-compose.yml defines six interconnected services:
| Service | Image | Purpose |
|---|---|---|
nginx |
Custom build | Reverse proxy, rate limiting, static caching |
leaderboard-web |
Artifact Registry | Spring Boot frontend with Thymeleaf |
lfs-data |
Artifact Registry | Spring Boot API with MongoDB |
mongodb |
mongo:7.0 | Document database |
redis |
redis:7-alpine | Session cache with LRU eviction |
cloudflared |
cloudflare/cloudflared | Tunnel client |
Each service includes health checks, restart policies, and proper dependency ordering:
leaderboard-web:
depends_on:
redis:
condition: service_healthy
lfs-data:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
start_period: 90s
rel_*) trigger production deploymentdocker compose upThe platform integrates with PGA Tour data feeds to provide:
Inspired by the original friend group that started it all:
Every group can customize their experience:
Built with Spring AI and Google Vertex AI Gemini:
The nginx configuration handles production traffic with proper security and performance tuning:
# Web frontend
server {
server_name leaderboardfantasy.com golf.leaderboardfantasy.com;
location / {
proxy_pass http://leaderboard_web;
proxy_set_header X-Forwarded-Proto https;
}
}
# API backend
server {
server_name api.leaderboardfantasy.com;
location / {
limit_req zone=api burst=50 nodelay;
proxy_pass http://lfs_data_api;
}
}
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# API: 10 requests/second with burst
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Login: 5 requests/minute (brute force protection)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# Trust Cloudflare proxy headers to get real client IP
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... (all Cloudflare IP ranges)
real_ip_header CF-Connecting-IP;
Database backups run twice daily via cron, uploading to Google Cloud Storage:
# Cron schedule (UTC)
0 0 * * * root /opt/leaderboard/scripts/backup-mongodb.sh
0 12 * * * root /opt/leaderboard/scripts/backup-mongodb.sh
The backup script:
mongodump inside the containerdocker exec leaderboard-mongodb mongodump \
--db=leaderboard-db \
--archive \
--gzip > mongodb-backup-${TIMESTAMP}.gz
gcloud storage cp mongodb-backup-${TIMESTAMP}.gz gs://lfs-mongodb-backups/
GCS lifecycle rules automatically clean up old backups, keeping costs minimal.
Every tagged release triggers automated deployment:
deploy:
name: Deploy to VPS
runs-on: ubuntu-latest
environment:
name: production
url: https://leaderboardfantasy.com
steps:
- name: Pull and deploy services
run: |
ssh ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} << 'EOF'
cd /opt/leaderboard/app
docker compose pull
docker compose up -d
EOF
docker compose pull fetches new imagesdocker compose up -d restarts servicesIf deployment fails, rollback is simple:
# SSH to VPS
docker compose pull leaderboard-web:previous-tag
docker compose up -d leaderboard-web
Leaderboard Fantasy was developed primarily through agentic AI orchestration—using AI agents for implementation while human judgment guided architecture and product decisions.
What would traditionally require:
Actual development: Nights and weekends over 3 months, with AI agents handling mechanical implementation while human judgment guided architecture and user experience.
Self-host when:
Use managed services when:
For single-server deployments, Docker Compose provides:
You don't always need Kubernetes.
No more:
Just run the tunnel container and configure hostnames in Cloudflare Dashboard.
Even on a budget:
A VPS can fail. Your data shouldn't be lost.
Leaderboard Fantasy proves that production software doesn't require production budgets.
A solo developer with:
...can build and operate a production SaaS for the cost of a Netflix subscription.
The spreadsheet is still saved in Google Drive—a reminder not just of where this started, but of how software development has fundamentally changed. Those friends are still playing, by the way. Now with custom trophies, years of history tracked automatically, and rivalries that have outlasted the original Excel file by years.
Want to play? Join at leaderboardfantasy.com
Curious about the development process? Read the full AI-assisted development story