Live at chess-mate.online
A production chess-coaching platform I built and operate alone.
Python · Django · React · AWS · PostgreSQL · Docker · Celery · OpenAI · OpenTelemetry · Grafana · Sentry · Stockfish · AWS Elastic Beanstalk · Sole builder and operator, Jan 2025 - present
970 commits · 20 months · 2,000+ automated tests · 10+ pilot users · 100+ batches analyzed
What it does
ChessMate imports a player's games from Chess.com or Lichess, analyzes them with Stockfish through an asynchronous pipeline, and turns the results into coaching: single-game feedback and batch reports that find recurring patterns across 5–30 games. It runs on AWS with Stripe-backed credits. 10+ pilot users have analyzed 100+ batches. I have built and operated every part of it: 970 commits over 20 months.

Why I built it
Chess sites show engine evaluations one game at a time. That tells you what went wrong in a single game. Improvement comes from patterns: the same opening mistake, the same endgame weakness, repeated across dozens of games. A human coach charges for that. ChessMate analyzes games in bulk and ranks those patterns into a training plan.
What I built
Architecture
Browser
React SPA
ALB
HTTPS
EC2 t3.small
Elastic Beanstalk · single Docker container
RDS PostgreSQL
users, PGN, report JSONB
External APIs
Chess.com / Lichess · OpenAI · Stripe
The whole platform runs in one Docker container on a single t3.small behind an ALB, with PostgreSQL on RDS. Game PGNs and analysis results live in Postgres (JSONB), not S3: at roughly 6–80 KB per game, storage is negligible and one datastore keeps queries and backups simple.
Two decisions and their tradeoffs
Stockfish is CPU-bound and shares a t3.small with the web server, so the pipeline runs one game at a time. A 10-game batch takes 30–50 minutes wall-clock and only one batch runs at a time. The gain: no CPU contention with API traffic, predictable memory, and a negligible marginal cost per batch. The UI shows progress per game and honest ETAs. Scaling to a dedicated worker waits until real demand exists.
Redis is both cache and Celery broker, bundled in the same container as Django. A redeploy wipes the cache and any queued tasks. I accepted that because the cache is rebuildable, batches are cancelable and retryable by design, and managed Redis would roughly double the monthly bill at pilot scale. That choice caused a real incident; the fix lives in the job design.
A production failure and the fix
A deploy restarted the single container while a batch was mid-analysis. The in-flight Celery task died with the worker, and the batch record stayed "in progress" forever. With everything in one container, any deploy could orphan whatever was running.
Every per-game task now carries soft/hard time limits (840s/900s). A cancel_batch command marks orphaned batches failed. Users can retry only the failed games without re-paying credits.
If your queue and workers can vanish on deploy, that constraint has to live in the jobs. Time-bound every task. Make every job state reachable by ops tooling. Give users a self-serve retry.
Ask me about