home menu

Farfield

Five small Go services, single binaries, content-addressed records, running on my homelab. The new backend for the site.

On this page

If you've followed along at all you'll be aware of the various evolutions this site has undergone. From Wordpress to a Ghost blog built around a template from Themeforest, Squarespace, Hugo, Gatsby, NextJS, and now Astro. And for each framework in the mix, there has been a revolving door of backend. Flat files, a private GitHub repo read over the GraphQL API, a single-user PDS. The frontend changed and the backend changed under it, year after year.

Obsidian as a CMS carried me the longest. Write in Obsidian, sync to a private repo, query GitHub's GraphQL API from the frontend, render the markdown. It was cheap, it was mine, and for a few years it did the job. The rough edges stayed rough. Tag aggregation lived in a hacky flat file. Images went through a plugin. There was no real notion of a record, just files in folders.

A friend had been building everything on a personalized Rust stack, andromeda and a thin slice of dependencies. I wanted that shape for my content. So I built my own CRUD apps. Not a new concept. This collection is mine.

farfield

farfield is a set of small, single-binary Go services. Each one is an HTML admin UI for writing and moderating content, plus a public JSON API the website reads.

apex serves the landing page. content holds collections, entries, and series fragments. feed holds short ephemeral posts. blobs stores image bytes and metadata on Cloudflare R2. A fifth service, backup, runs tailnet-only and snapshots every database into R2 on a schedule.

The stack is the Go standard library plus one dependency: modernc.org/sqlite, a pure-Go SQLite driver. No cgo, so every build is a static binary. HTTP is net/http, templates are html/template, assets get embedded with embed. It's a Go adaptation of andromeda's shape, standard library first, one deliberate dependency.

Three ideas

Every record carries a CID, a sha-256 hash of its content. The slug is the stable key and never changes. The CID changes whenever the content does, which makes change detection, ETag caching, and verification fall out for free. Content addressing on a PDS sold me on this, and I wanted it here too.

A private IPFS

I do devrel at Pinata, so content addressing is the day job. IPFS is good. The DHT, the gateways, the pinning network, all of that is more than a personal site needs.

farfield keeps the part that earns its place. The CIDs are CIDv1, sha-256, the same identifiers IPFS would mint. They live as columns in SQLite and serve over plain HTTPS. Content addressing without the network. A private IPFS, shaped to one site.

A blob's CID is the hash of its bytes, so /blobs/{cid} is immutable and caches forever.

How it runs

docker compose builds each app into a distroless/static image. Production is my homelab server, exposed through a Cloudflare tunnel. Reads are public and send Access-Control-Allow-Origin: *, so Astro fetches them straight from the browser. Writes need an X-API-Key.

The site is rebuilt around this now. Astro reads three JSON APIs instead of cloning a markdown repo at build time. Entry bodies embed blob:// and series:// URIs that resolve to real URLs before rendering.

The home server those services run on is its own story. That's a post for later.

collection
posts
rkey
1779066375000-farfield
record cid
bafkreicnzwpmnv52as5mcaxtdbzzirxcjyqx7ovaa3zmln25no5ac75xwa
record
https://content.farfield.systems/api/entries/1779066375000-farfield
created
updated