Case study · Setlist · setlist.gg

Runningan online game,alone.

Setlist is a passion project. It is my first game. I am not presenting it for its player count, but for what it taught me: designing an experience, making game design decisions, and keeping an online service running with no team behind me.

  • 100%uptime since launch (49 h, internal Prometheus measurement)
  • 0server errors across 60,356 requests
  • 271 msfor game actions at the 95th percentile
  • 1 smaximum data loss in normal operation (continuous replication)
  • 2.1 sto restore the full database, a real, timed restore
  • €8a month for the whole production infrastructure

Measured on production on 13 October 2026, 49 hours after launch, read-only (Prometheus, Traefik, Litestream).

The game in two sentences

Every card is a real piece of music, with its artwork, a 30-second preview and a rarity from Common to Legendary. You dig through crates like at a record shop, complete albums, trade, and face your friends in blind tests. A catalog of about 18,000 tracks, refreshed automatically every week, in French and English.

My personal goal: helping people discover music and other cultures. Every week the game highlights three albums, two classics and one discovery, each with its story.

First lines of code on 25 September 2026, version 1.0 live on setlist.gg on 11 October, 16 days later. Since then, one production release a week, on Wednesday at 2 am.

My role: setting the direction

I held every role of a small studio: game designer, architect, ops and support.

I wrote the code with an AI assistant (Claude Code), and I say so openly. What the AI does not do for me is decide: what the game should be, what is fair for players, what ships to production and when. Every feature starts from an intent I formulate, and every release goes through my approval.

I also set up a systematic review: a second agent reads every batch of changes before release, for security, performance and regressions. On the latest version, four review passes produced fourteen fixes, all merged before shipping. Four real vulnerabilities were stopped before production, including account creation without a confirmed email through OAuth and a load amplification through the "typing" signal. None of them reached players.

What I learned about game design

I had never made a game. The hard questions were not technical:

  • The economy. Players earn "notes" and spend them on digs. An over-generous reward devalues everything else. Before any new bonus, I look at real prices and the median player wallet.
  • Rarity. It does not come from an arbitrary roll: it follows each track's lasting popularity (Deezer, Last.fm, Wikipedia, longevity). A Legendary is a song everyone has known for decades.
  • Frustration. A pity counter guarantees an Ultra Rare after 30 digs without one. Enough to avoid frustration, not enough to make rarity meaningless.
  • Dopamine, measured. A Legendary gets its full-screen celebration. An Ultra Rare gets a quieter effect. And players who dislike animations can turn them off in the settings.
  • No real money. The premium mode, Backstage, is paid only with in-game notes. That is a choice: the music API I use is not commercial, and I do not want a pay-to-win game.
  • Abuse resistance. Referrals, trades, rewards: every mechanic that gives something out was designed not to be farmable (limited accounts per network, rewards paid once, sensitive actions restricted to confirmed email addresses).

The architecture

Deliberately simple, because one person has to be able to understand and repair all of it.

Setlist architecture diagram: players, production VPS, backup and staging homelab. Internet Production VPS · OVH · Docker Swarm · €8 a month Homelab WireGuard tunnel Playersbrowser, iPhone app Deezer CDNartwork, 30 s previews Music sourcesDeezer, MusicBrainz, Last.fm,Wikidata, Genius Discord · Google · BrevoOAuth, announcements, emails TraefikHTTPS, Let's Encrypt, single entry SetlistPocketBase · 119-route APIreal time SQLite40 MB Litestream setlist.gg landing Static sites Monitoring (private network) Prometheus Alertmanager Logs: Loki, Alloy Grafana (private) Backups7 days of restore points DeliveryWednesday 2 am, auto rollback Stagingfull copy, tests only Watchdogexternal probe every minute HTTPS every second image and deploy Setlist architecture diagram: players, production VPS, backup and staging homelab. Internet Playersbrowser, iPhone app HTTPS Production VPS · €8 a month TraefikHTTPS, Let's Encrypt, single entry SetlistPocketBase · 119-route APIreal time SQLite40 MB Homelab · WireGuard tunnel Backups7 days of restore points DeliveryWednesday 2 am, auto rollback

Monitoring and alerts: Prometheus, Alertmanager, Discord.

The game

  • Backend: PocketBase (Go and SQLite), with all game logic in JavaScript hooks: 66 files, about 11,000 lines.
  • Frontend: SvelteKit and Svelte 5, an installable web app on iPhone, about 24,000 lines, two languages and more than 1,400 translated strings.
  • Real time: messages, read receipts, a "typing" indicator, a friends activity feed.
  • 45 versioned, replayable schema migrations: no database change is ever made by hand.

Production

  • One OVH VPS (2 vCPU, 4 GB of RAM, €8 a month), on Docker Swarm: 14 containers in four separate stacks (Traefik and Let's Encrypt edge, monitoring, game, websites).
  • The game uses 0.05 CPU cores on average, with a measured peak at 0.9.
  • Prometheus, Grafana, Loki, Alertmanager, 12 alert rules, each with its likely cause and diagnostic command, and a monitoring tab built into the game's admin panel.
  • Database replicated every second (Litestream) to my homelab over a WireGuard tunnel: 7 days of restore points, one full snapshot a day.

Staging

  • My homelab hosts a full copy of the game, built from the development branch. Everything is tested there before it reaches production.
  • The real game rules, but no public accounts: I never test in production.

A custom game API

All of Setlist's logic goes through an API I designed on top of PocketBase: 119 routes, 97 of them restricted to signed-in players. It is what enforces the game rules, server-side, without ever trusting the client: card draws and the pity counter, blind tests and the ladder, trades and the auction house, groups, real-time messages, rewards and abuse prevention.

It relies on six external sources (Deezer for the catalog and previews, MusicBrainz, Last.fm, Wikidata and Genius for release years, popularity and credits, Discord for the community), with queues and quotas to respect their limits.

It also serves public pages for search engines (card and artist pages, sitemaps), and exposes its own metrics to Prometheus on an endpoint restricted to the private network or protected by token. Per-IP rate limiting protects the whole thing. In numbers: 68 ms median latency on game actions, 271 ms at the 95th percentile, measured by Traefik over the first 49 hours.

Choices and their trade-offs

SQLite rather than PostgreSQL
One machine, one file, simple backups, very fast queries at this scale. The trade-off: no horizontal scaling. If the game ever grew seriously, that would be the first project.
Docker Swarm rather than Kubernetes
Swarm does what I need (rolling deployments, automatic rollback, secrets, networks) without the operational weight of Kubernetes. For a service on a single machine, Kubernetes would have been cost without benefit.
A weekly release train
At first I deployed several times a day. I stopped: everything validated waits on a branch, and a script ships it all on Wednesday at 2 am. If there is nothing new, there is no release. The script builds a clean copy of the code, deploys, checks the game's health, and rolls back to the previous version on its own if that check fails. Players get regular updates without living with constant instability.
Few alerts, but real ones
An alert that fires for nothing ends up ignored. After a series of false positives, I rewrote every rule: an outage is only declared after 5 minutes with no response at all, slowness is only measured on successful requests, and reminders are spaced out. I also added capacity alerts: they tell me, with numbers, when it is time for a bigger server, rather than finding out during a player peak.

A real incident

On 13 October, my homelab shut down during the night. The game itself saw nothing: it runs on the VPS. But the database replication towards the homelab got stuck on a dead connection, and it did not recover when the server came back.

My alert checked that the replication service was running, not that it was actually copying: nothing fired. I found out the same day, while preparing an economy analysis from a copy of the database: the data was a day old.

In numbers: zero data lost, about 25 minutes from discovery to a verified restore. After the repair, I added an alert on actual replication lag, and made the silent copy script loud about failure. The same blockage would now be detected within 30 minutes, against about fifteen hours that day. The lesson I keep: watching that a process runs does not tell you it is doing its job.

What I would do differently

  • A second backup destination, independent from my homelab, from day one.
  • An automatic, daily restore test, rather than discovering a backup is unusable on the day you need it.
  • The weekly release train from week one, instead of adopting it after a few deployments too many.

What Setlist says about how I work

I design systems a small team can actually operate. I prefer a simple, well-monitored architecture to an impressive one nobody understands at 3 am. And I discovered I enjoy designing a player's experience as much as the infrastructure that runs it.

Play on setlist.gg

Let's talk about your project.

Reply within 24 hours. On site in Toulouse, France, remote worldwide.