2024 — present · Go · PostgreSQL · html/template · Tailwind
carterpeaslee.com
Portfolio and booking site for artist Carter Peaslee — the project where most of my patterns were born.
This is the oldest site I run, and the one with the most of me in it. It's a portfolio and booking site for Carter Peaslee: show listings, venues, a bio, and a contact form that lands in email. Nothing exotic on the surface — the interesting parts are underneath.
Three stacks in two years
I started in January 2024 on Gin. This and a Discord bot were among the first real things I built, so I was taking recommendations — and everything pointed at a framework. Four months later I moved the whole thing to Echo and put GORM in front of Postgres. That version ran for two years and did its job.
By 2026 Echo was doing routing and not much else: the gateway and Cloudflare
had taken over gzip and rate limiting, and gomail had been unmaintained
since 2016. It was carrying less every year and still deciding the shape of my
code. So in August 2026 I rewrote the site on the standard library and threw
all of it out — Echo, GORM, JWT cookies, gomail, godotenv. The rewrite is
where the current routing, session handling, template renderer and migration
runner come from, and most of them have since moved into sitekit. The database
came through untouched.
Decisions I'd defend
The standard library is all you need. The backend is Go's net/http with
the 1.22+ method-and-pattern routing — no framework. Handlers are plain
functions, middleware is plain function composition. Since the rewrite there
has been nothing I couldn't build this way, and nothing to upgrade around.
Postgres through database/sql, migrations in the binary. GORM went out
with the rewrite, replaced by plain database/sql over pgx against the schema
that was already there — the baseline migration is CREATE TABLE IF NOT EXISTS, so the live database was never touched. The schema now ships as
embedded SQL migrations applied at boot, so a deploy is one binary and nothing
else. No migration tooling to run by hand, no drift.
Real HTML. Pages are server-rendered Go templates with proper meta tags, so links unfurl correctly and search engines see actual content. Tailwind handles the styling; the browser gets HTML, not a bundle.
How it ships
A git tag is the whole release process. CI lints the Go, the templates and the JavaScript, runs the tests, pulls the audio clips down from Nexus, builds one static binary, bakes it into an image and pushes it to my registry. Then I change a single line in the GitOps repo and Argo CD takes it from there — automated sync with prune and self-heal, so the cluster and the repository cannot quietly drift apart.
In the cluster it is one pod. An init container waits on Postgres so a cold bootstrap comes up in the right order, and an nginx sidecar terminates TLS so the gateway reaches the app encrypted rather than in plaintext across the cluster. Secrets arrive through external-secrets instead of living in a repo, and Cloudflare sits in front of the whole thing. Two git repositories describe the entire site; the only things outside them are the secrets and the audio.
What it seeded
The patterns that worked here — admin sessions, SEO helpers, the mailer, Turnstile verification — got extracted into sitekit, a shared library I now use across every site I run, including this one. The renderer serving the page you're reading is a direct descendant of carterpeaslee's.
2026: the jukebox
The idea came from an ad. Reels and shorts kept serving me those refrigerator magnets that are built like a tiny record player — a disc that actually spins on the fridge door while it plays a song. They stuck with me. Carter's releases were sitting on the site as a flat list of links, and that list was never going to make anyone want to touch it.
So I built a jukebox: a honky-tonk cabinet in warm walnut and oak, brass hardware, burlap stretched over the grille, and a West Texas plate bolted on at the bottom. Carter's name arcs across the top in western lettering. The five release records float in a semicircle above the platter, the title strips sit behind glass, and there's a keypad under the transport. Punch B then 3, on screen or on a real keyboard, and Wildflowers leaves its slot on the arc, really drops onto the platter, and spins.
It plays thirty-second previews rather than full songs — Carter's call. None of the audio is in git. The clips live in a Nexus raw repository; a script pulls them at build time and checks every file's SHA-256 against a manifest, then they get compiled into the binary itself alongside the templates and the CSS. The full-length masters sit in the same repository under a different path and never ship. A deploy is still one binary and nothing else.
The whole thing is one <audio> element, a few hundred lines of plain
JavaScript type-checked as TypeScript, and CSS. The gateway's CSP allows no
inline scripts and no third-party media, so there is no embedded player and
nothing loaded from anyone else. The records are the original cover art
clipped into circles, which is what tells them apart at a glance. The tubes
are borders with a glow. The drop is a transition that undoes each record's
place on the arc. Turn on reduced motion and you get the same jukebox with the
animation switched off.