The Team
Petty Theft Row is a men's slowpitch softball team based in New York City. We have played in a number of recreational leagues across the city since spring, 2023.
Tech Stack
Hi, I'm Andrew. I built this site because I like softball and I like software and I couldn't help myself.
Elixir and Phoenix LiveView, backed by SQLite. I picked Elixir because I like writing it. The pattern matching, the pipe operator, the way you end up with these small functions that just click together. I reach for it on personal projects because it makes me want to keep building. It also happens to give you a supervision tree and the whole OTP runtime underneath, which is absurd firepower for a softball website, but it's there if I ever need it.
LiveView is the other half of that. A page is a mount function and a template. When somebody updates the score after a game, every open browser just gets it. No websocket code to write, no client state, nothing. I wanted something I could run on a single box without thinking about it, and this is pretty much the simplest stack that gives you real-time UI for free.
SQLite was partly a "why not" decision. We don't have concurrent write pressure, the whole database fits in a pocket, and backups are just copying a file to S3. I've run Postgres for work stuff and it's fine, but for a site like this it would be maintaining infrastructure for the sake of maintaining infrastructure.
The front end is a monospace grid we wrote in plain CSS. Character widths and line heights, no border radius anywhere. Tailwind does the utility stuff. I'm not a designer, but it turns out if you commit to a single font and a strict grid you can skip most of the decisions that slow you down.
Operations
deploy (on push to main)
│
├─ 1. GitHub Actions CI
│ compile, assets, mix release
│
├─ 2. rsync tarball to VPS
│
├─ 3. Ansible playbook
│ ├─ stop inactive slot
│ ├─ extract release
│ ├─ run migrations
│ └─ start new slot
│
├─ 4. health check GET /healthz
│
├─ 5. swap Traefik route
│ update dynamic YAML config
│
└─ 6. stop old slot
done ── zero downtime
db backups (daily 06:00 UTC)
│
├─ 1. sqlite3 .backup on VPS
├─ 2. gzip + stream to S3
└─ 3. prune backups, keep 30
Architecture
┌────────────┐
│ Browsers │
└─────┬──────┘
│ HTTPS
▼
┌────────────────────────────┐
│ VPS (Debian) │
│ │
│ ┌──────────────────────┐ │
│ │ Traefik :443 │ │
│ │ Let's Encrypt TLS │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────────┴───────────┐ │
│ │ blue/green deploy │ │
│ └────┬────────────┬────┘ │
│ │ │ │
│ ┌────┴─────┐ ┌────┴────┐ │
│ │ :4001 │ │ :4002 │ │
│ │ blue │ │ green │ │
│ │ Phx+LV │ │ Phx+LV │ │
│ └────┬─────┘ └────┬────┘ │
│ └──────┬─────┘ │
│ │ │
│ ┌───────────┴──────────┐ │
│ │ SQLite │ │
│ │ ecto_sqlite3 │ │
│ └──────────────────────┘ │
│ │
└─────────────┬──────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
┌────────┐┌───────┐┌────────┐
│ S3 ││ Brevo ││ Open- │
│ media ││ email ││ Meteo │
│ ││ + SMS ││ wx API │
└────────┘└───────┘└────────┘