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 │
└────────┘└───────┘└────────┘