Leaderboard Fantasy
A fantasy golf platform that evolved from Excel to GCP to a cost-optimized VPS. This is the story of building a production SaaS application with agentic AI development, then migrating from managed cloud services to a self-hosted Docker stack—proving that prudent engineering decisions and modern tooling can deliver enterprise-grade reliability on a startup budget.
Leaderboard Fantasy: The Art of Frugal Engineering
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.
The Origin Story
Like many great products, Leaderboard Fantasy began with a personal pain point. Managing a fantasy golf pool for friends meant:
- Manually copying leaderboards every tournament weekend
- Calculating standings with increasingly complex formulas
- Sending updates to the group via text and email
- Hours of work for what should have been simple fun
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 Evolution: From GCP to VPS
Phase 1: The Cloud-Native Era
The first production version was built on Google Cloud Platform, embracing every managed service available:
- Google App Engine for the web frontend
- Google Cloud Run for the API backend
- MongoDB Atlas for managed database
- Redis via Memorystore for sessions
- Terraform for infrastructure-as-code
- Google Secret Manager for credentials
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.
Phase 2: The VPS Migration
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 Architecture: Docker + Cloudflare Tunnel
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 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
Why Cloudflare Tunnel?
The VPS has no ports exposed to the internet except SSH for management. All web traffic flows through Cloudflare Tunnel:
- Zero public attack surface: No ports to scan, no IPs to DDoS directly
- Free SSL/TLS: Cloudflare handles certificate management
- Built-in DDoS protection: Cloudflare's edge network filters malicious traffic
- Simple networking: No NAT configuration, no firewall rules to manage
# Traffic flow
Internet → Cloudflare Edge → Tunnel → nginx → Application Services
Docker Compose 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
The Tech Stack
Backend (lfs-data)
- Java 21 with Spring Boot 3.5
- MongoDB 7.0 for document-oriented data storage
- Spring AI with Google Vertex AI Gemini for AI-powered chat
- Spring Data MongoDB for repository pattern
- ULID for time-sortable unique identifiers
Frontend (leaderboard-web)
- Spring Boot 3.5 with Thymeleaf for server-side rendering
- Spring Security 6 with OAuth2 (Google, Facebook)
- Spring Session with Redis for distributed sessions
- Tailwind CSS for utility-first styling
- Spring Actuator for health monitoring
Infrastructure
- Linux VPS (Ubuntu) for compute
- Docker Compose for container orchestration
- Cloudflare Tunnel for secure ingress
- nginx for reverse proxy and rate limiting
- Google Artifact Registry for container images
- Google Cloud Storage for database backups
CI/CD
- GitHub Actions for automated builds and deployments
- Docker multi-stage builds for optimized images
- Automatic deployment: Tagged releases (
rel_*) trigger production deployment - SSH-based deployment: GitHub Actions SSHs to VPS and runs
docker compose up
Key Platform Features
Live Tournament Scoring
The platform integrates with PGA Tour data feeds to provide:
- Real-time leaderboard updates during tournaments
- Automatic score calculations based on customizable rules
- Support for weather delays, playoffs, and international events
- Mobile-optimized views for checking scores on the course
Groups: Persistent Communities
Inspired by the original friend group that started it all:
- Create public or private groups
- Invite members via email
- Leader/member role hierarchy
- Group message boards for trash talk and strategy discussion
- Season-long rivalries and persistent standings
Flexible Contest Engine
Every group can customize their experience:
- Configurable roster sizes (4-20 players)
- Adjustable scores-to-count rules
- Custom missed cut penalties
- Public contests for global competition
- Private contests for friends only
AI-Powered Chat Assistant
Built with Spring AI and Google Vertex AI Gemini:
- Player Statistics: Career performance, recent form, tournament history
- Roster Analysis: Evaluate your picks against field strength
- Head-to-Head Comparisons: Compare any two players across metrics
- Smart Alternatives: AI-suggested replacements for injured or underperforming players
- Context-Aware Advice: Responses tailored to specific tournaments and contests
nginx Configuration Deep Dive
The nginx configuration handles production traffic with proper security and performance tuning:
Domain Routing
# 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;
}
}
Security Headers
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";
Rate Limiting
# 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;
Cloudflare IP Trust
# 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;
Automated Backups
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:
- Runs
mongodumpinside the container - Compresses to gzip archive
- Uploads to GCS bucket
- Sends failure notifications via Resend API
- Cleans up temporary files
docker 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.
Deployment Pipeline
GitHub Actions Workflow
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
Deployment Process
- Build: GitHub Actions builds Docker images
- Push: Images pushed to Google Artifact Registry
- SSH: Workflow SSHs to VPS
- Pull:
docker compose pullfetches new images - Deploy:
docker compose up -drestarts services - Verify: Health checks confirm services are running
Rollback
If deployment fails, rollback is simple:
# SSH to VPS
docker compose pull leaderboard-web:previous-tag
docker compose up -d leaderboard-web
The AI Development Story
Leaderboard Fantasy was developed primarily through agentic AI orchestration—using AI agents for implementation while human judgment guided architecture and product decisions.
The Workflow
- Specification: Detailed feature specs written with Claude, divided into 4-8 hour implementation units
- Implementation: Claude Code reads the repository, analyzes patterns, implements end-to-end
- Quality Gates: GitHub Actions runs linting, tests, and security checks
- Review: Human review maintains final say on architecture and quality
- Deployment: Tagged releases trigger automated production deployment
Velocity Multiplier
What would traditionally require:
- Solo developer: 4-6 months of full-time work
- Small team: 3-4 people, 2-3 months
Actual development: Nights and weekends over 3 months, with AI agents handling mechanical implementation while human judgment guided architecture and user experience.
Lessons in Frugal Engineering
Know When to Self-Host
Self-host when:
- Traffic is predictable and modest
- You have the skills to operate the service
- Cost savings justify the operational overhead
- The service isn't business-critical 24/7
Use managed services when:
- You need guaranteed SLAs
- Scaling is unpredictable or bursty
- The operational burden exceeds the cost savings
- Your time is better spent on product
Docker Compose is Production-Ready
For single-server deployments, Docker Compose provides:
- Service dependency management
- Health checks and automatic restarts
- Volume persistence
- Network isolation
- Easy rollbacks
You don't always need Kubernetes.
Cloudflare Tunnel Simplifies Everything
No more:
- Managing SSL certificates
- Configuring firewalls
- Setting up NAT rules
- Worrying about DDoS
Just run the tunnel container and configure hostnames in Cloudflare Dashboard.
Backups Are Non-Negotiable
Even on a budget:
- Automate backups (cron + script)
- Store offsite (GCS is cheap)
- Test restores periodically
- Set up failure notifications
A VPS can fail. Your data shouldn't be lost.
Future Roadmap
- Payment Processing: Premium features and prize pools
- Native Mobile Apps: iOS and Android for on-course score checking
- Advanced Analytics: Historical trends, predictive modeling
- API Platform: Public API for third-party integrations
The Real Takeaway
Leaderboard Fantasy proves that production software doesn't require production budgets.
A solo developer with:
- Clear vision and product sense
- Appropriate AI agent orchestration
- Pragmatic infrastructure decisions
- Basic operations knowledge
...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