Building a PDS the Hard Way
Agent IO
March 18, 2026
A deep dive into ATProto Did I say that I was nerd-sniped by the AT Protocol ? Maybe not loudly, but my close friends knew that I'd thrown myself into a new side project and may have wondered about the wisdom of that (frequently I've had side projects of side projects of side...). "How hard could it be," I wondered, and at the beginning of February I decided that it wouldn't be that hard and that I could probably have something working before I left for Vancouver for ATmosphereConf . But it was challenging. It helped a lot to have an instance of the Bluesky PDS running behind IO so that I could observe its traffic, and it also helped to have already built the client side of ATProto OAuth into IO , and to have just built slink , which gave me a way to interact with XRPC in Go on my own terms. However, I didn't vibe code it and kept with my characteristic distaste for dependencies , so I wound up writing some key parts (like CID, CBOR, CAR, MST operations and OAuth) by hand and from scratch. One other thing that helped a lot was to start with the Bluesky PDS data structures. As Fred Brooks wrote: Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious. Smile, a PDS I haven't decided what I want to do with this other than use it myself for a while. Like IO, it has a TUI and an SSH control interface. Just looking at the start screen raises lots of ideas of things that could be added to it. I call the PDS smile because originally I thought I was building something very personal for a single user who, for whatever reason, might want to run multiple repos. I thought of it as a hobbit-hole, which Tolkien called a smial (💡!), but I didn't want to forever be correcting the spelling for people who heard the name, and smile isn't a bad thing to present to the world. Currently I have one instance of smile that I run on my laptop and publish to the world using a Digital Ocean droplet, IO , and an SSH reverse tunnel. Did you know about these? All I do is run this on my laptop: where cloudhost is the name of my droplet in my Tailnet. Then when I run smile locally, it listens to port 8080, which the tunnel makes available on port 8080 of my droplet, which IO publishes using an ingress . (That port is firewalled in case anyone is tempted to hit it directly.) Commit History Here is the commit history for what I've done so far.
Discussion in the ATmosphere