{
"path": "/3mgimvh7ygc2j",
"site": "at://did:plc:ulg2bzgrgs7ddjjlmhtegk3v/site.standard.publication/3mgbpqoas222x",
"tags": [
"pds",
"atproto",
"rivet",
"actor model",
"how to"
],
"$type": "site.standard.document",
"title": "How to Make a PDS - Part 1",
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"id": "019cbbc4-6c21-744d-8310-84c957225a53",
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.bskyPost",
"postRef": {
"cid": "bafyreicl7mru6yyp5x35hked6oovu7gcou5vafgmc3glswtghakf45kwfe",
"uri": "at://did:plc:ulg2bzgrgs7ddjjlmhtegk3v/app.bsky.feed.post/3mgdcsuhvak2q"
},
"clientHost": "bsky.app"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.bskyPost",
"postRef": {
"cid": "bafyreib7axd47nafm4r77prurugv5ijqm5ehwh3mxoomjwngulr7qu3jwa",
"uri": "at://did:plc:ulg2bzgrgs7ddjjlmhtegk3v/app.bsky.feed.post/3mgdcsuhz5k2q"
},
"clientHost": "bsky.app"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "This won't be exactly what I imagined, but it'll be something."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.blockquote",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Note: It's actually quite possible I never finish this. So while it will be someting it may not be anything more than what you find below. We'll see."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 80,
"byteStart": 62
},
"features": [
{
"uri": "https://github.com/samuelgoto/micropod",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Recently I ran into someone who made a personal, experimental PDS implementation."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.bskyPost",
"postRef": {
"cid": "bafyreighwtrvv5tjr6jm7qyi5w2d4d47tvv6wwyf3azaxnabw5autkiwnq",
"uri": "at://did:plc:bftbdgcbe4zclvpkboe4hleq/app.bsky.feed.post/3mfq6drale22u"
},
"clientHost": "bsky.app"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "That seems like a pretty good sign as far as how possible it is to make one."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 58,
"byteStart": 46
},
"features": [
{
"did": "did:plc:cyqufxsezk33hqulcilckna6",
"$type": "pub.leaflet.richtext.facet#didMention"
}
]
},
{
"index": {
"byteEnd": 93,
"byteStart": 88
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "On top of my general interest in the idea, at @roomy.space we recently realized that we might end up essentially making our own PDS with extra features for communities, privacy & realtime messaging. We're finding we agree with more of ATProto's design decisions than we thought we might."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 68,
"byteStart": 63
},
"features": [
{
"uri": "https://rivet.dev/",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Anyway to learn the ropes I'm going to try to make a PDS using Rivet, and awesome, distributed actor framework. In the interest of the blog series, I figured I'd journal about it as I tried it out, too."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Now the goal here isn't to make a PDS from scratch. I don't want to implement an MST or anything else really that I don't have to. So we're going to try and re-use ATProto's reference implementations of everything we can, while maintaining full controll over the networking, storage, and overall design of the PDS ourselves."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I've never done this before, so again this is not so much a tutorial as a journal, but it might be interesting. Let's get started!"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 1 - Read the Docs"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 54,
"byteStart": 43
},
"features": [
{
"uri": "https://atproto.com/specs/atp",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 82,
"byteStart": 74
},
"features": [
{
"uri": "https://atproto.com/specs/atp",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "First let's head to the ATProto docs site, atproto.com. In particular the overview of the specs section looks like a good place to start."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/atp",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Overview: I'm pretty familiar with most of ATProto, and after reading the overview, there wasn't anything new to me, so I moved on."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 10,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/data-model",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 35,
"byteStart": 29
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "Data Model: Some good info I mostly already knew. Might need to come back to some details during implementation, but libraries will be handling most of this I think."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/lexicon",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Lexicon: Barely skimmed this 'cause I've read it before."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/cryptography",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Cryptography: Skimmed it. Will use libraries mostly for this."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/account",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Accounts: Read through this. It had some good info on accounts and their statuses and how the network is expected to respond to active or inactive accounts."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 10,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/repository",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Repository: Pretty meaty section worth reading through. I skimmed some of the MST implementation details, but got a good overview of how the repository is implemented."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 11,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/blob",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Media Blobs: Easy to read-through. Has good considerations about security and blob handling."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 6,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/label",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Labels: These are interesting, but I only skimmed the page for now. I don't think I need to worry about labels just yet."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/xrpc",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "XRPC: Good to know most of the stuff in here."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/oauth",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Oauth: The infamous OAuth. I read most of this, but skimmed over some of the details. I'm going to try and get a library to do this for me if at all possible."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 11,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/permission",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Permissions: Skipping for now, I've mostly read it before."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/event-stream",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Event Stream: Skipped through most of this, but got the general idea."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"uri": "https://atproto.com/specs/sync",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Sync: Very interesting. I found it cool that you can efficiently sync and verify ATProto repos. That could be really good for live, incremental backups of your PDS."
},
"children": []
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 66,
"byteStart": 63
},
"features": [
{
"uri": "https://atproto.com/specs/did",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 74,
"byteStart": 68
},
"features": [
{
"uri": "https://atproto.com/specs/handle",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 80,
"byteStart": 76
},
"features": [
{
"uri": "https://atproto.com/specs/nsid",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 86,
"byteStart": 82
},
"features": [
{
"uri": "https://atproto.com/specs/tid",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 98,
"byteStart": 88
},
"features": [
{
"uri": "https://atproto.com/specs/record-key",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 105,
"byteStart": 100
},
"features": [
{
"uri": "https://atproto.com/specs/at-uri-scheme",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "I'm pretty familiar with these so I just skipped this for now: DID, Handle, NSID, TSID, Record Key, at://."
},
"children": []
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "And there! I've sort of read the ATProto spec! Honestly it's not that bad. Most of it reads easy enough. I can wait to get into the details on some of it until later. It's kind of a mild, pleasant surprise. I didn't think it would be that bad, but I was suprised how lean the protocol actually is."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Now I've got to figure out what to work on first."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Interlude - More on Rivet"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 46,
"byteStart": 41
},
"features": [
{
"uri": "https://rivet.dev",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 94,
"byteStart": 82
},
"features": [
{
"did": "did:plc:cyqufxsezk33hqulcilckna6",
"$type": "pub.leaflet.richtext.facet#didMention"
}
]
},
{
"index": {
"byteEnd": 185,
"byteStart": 174
},
"features": [
{
"uri": "https://en.wikipedia.org/wiki/Actor_model",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "I just wanted to drop a quick note about Rivet here and why I'm trying it out. In @roomy.space we're realizing that the very event-driven nature of it fits very well into an actor model."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "As we've looked into doing more integrations with other ATProto apps, the ability to make new integrations and server-powered functionality on top of Roomy quickly has shown it's importance."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Rivet seems like a very powerful framework for making applications using an actor model, and it looks like it'll be fun to try out, so I'm playing with it for this week-end project."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 18,
"byteStart": 12
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
},
{
"index": {
"byteEnd": 132,
"byteStart": 128
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "Rivet looks really cool to me, but I want to try it out to see if it's as cool as I think it is. I hear a lot of developers who love how easy cloudflare makes things, and yet feel afraid of Cloudflare as being the internet's off-switch if everybody uses it. Rivet is also interesting because it seems like an Open Source alternative to Cloudflare workers."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 45,
"byteStart": 38
},
"features": [
{
"uri": "https://railway.com/",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Random side note, I've also found the Railway cloud since it was mentioned in the Rivet deployment options, and I'm really liking it so far."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "What Next?"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "OK, so I've read the docs, and I need to pick what to work on first."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 118,
"byteStart": 113
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "I think OAuth is probably one of the worste things to have to get working. Since it's one of the first steps for using the PDS for anything, I think I'll start on that. Hopefully I can re-use an existing authorization server library or something."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "That's for the next post!"
}
}
]
}
]
},
"description": "So you want to make a PDS? Or maybe you want to watch some poor fool try? In this series I might make a PDS, or not.",
"publishedAt": "2026-03-07T20:07:02.687Z"
}