Self hosting a PDS isn't hard (so far) • Cory DransfeldtSkip to main content
July 17, 2026Self hosting a PDS isn't hard (so far)<br>developmentwebdevtechatprotobluesky<br>/posts/2026/self-hosting-a-pds-isnt-hard-so-farDue to my compulsive need to self-host whatever I possibly can1, I decided to set up a self-hosted PDS. All that's on it so far is my Bluesky profile data, but it was a surprisingly painless process.Why bother? Well, I get to own my identity (DID) and my data. The relay and AppView remain Bluesky's, but the authoritative copy of my account is mine. I already syndicate from my site to Bluesky and controlling the PDS is the last part of this infrastructure I can reasonably control.I've been bolting features on to my own CLI to manage my infrastructure for a bit2. I use Hetzner for hosting and it wraps their hcloud CLI for my own convenience. I spun up a CX23 box with backups turned on and then ran another CLI command to bootstrap the server.The bootstrap configures the shell, installs Tailscale and other dependencies and clones a repository that contains a shared set of infrastructure dependencies.The server is running only two things: the canonical ghcr.io/bluesky-social/pds container maintained by Bluesky and Caddy. I use forwardemail.net for transactional emails.With the server provisioned, I updated the _atproto record for my domain and added a pds A record. I use DNSControl to manage DNS records, so this only took a few minutes.The migration<br>Migrating my data from Bluesky's PDS to my own was the only part of this process that made me a little nervous. I needed to preserve my DID, followers and just over 1,000 account records. I used Bluesky's goat tool to manage this process.I generated a recovery key using goat key generate --type k256Logged in to my old (Bluesky) account: goat account login -u -p --auth-factor-token Created a PDS invite for myself using my CLI (or SSH into the box and run pdsadmin create-invite-code).Ran the migration — it pauses and emails a PLC confirmation code that you paste into the prompt:goat account migrate \<br>--pds-host \<br>--new-handle \<br>--new-password "" \<br>--invite-code ""<br>Added my recovery key to the identity so it's my highest-priority rotation key (this also emails a confirmation token): goat account plc add-rotation-key --firstIt ran faster than I expected and worked without any issues (shocking, I know). I syndicated a post, opened my profile in an incognito window and it loaded without issue. I also verified that DID doc #atproto_pds pointed at my new host and threw my private recovery key in my password manager.Cleanup<br>The last step was to add support for a custom PDS URL to my site and the PWA I built myself to browse Mastodon and Bluesky simultaneously. This consisted of a migration to update the database schema to save the URL and the corresponding field in the UI. The app passwords I use for both also needed to be recreated on the new PDS which I did from the aforementioned CLI.I have yet another server running which, at this point, I'm starting to collect like trading cards. My ATProto data sits on its own server, free to be used with any apps that support it (for my purposes that's only Bluesky — for now). It was a surprisingly straightforward process and I'm thankful for that.I'm still getting my head around ATProto, but I appreciate that you can own your own data, if not all of the application infrastructure. I was used to the Mastodon mental model where, while you can migrate between instances, not all data comes with you. You own the identity but, perhaps, not all of the content. You can't own everything in either case, for now, but it feels like we're heading in the right direction.Except email. ↩︎More on that in a future post, probably. ↩︎
If you find this useful, feel free to buy me a coffee.<br>Related posts<br>July 7, 2026Implementing standard.site on a nonstandard site<br>Now that this site is written in Go, I've turned to restoring and adding more features connecting it to the open web. Wh…<br>April 6, 2026Battling bots<br>I've already blocked entire countries to combat scrapers, I update my robots.txt for well-behaved bots (and 403 any incl…<br>December 3, 20242024 in review<br>Wow, what a year. To address the elephant in the room up front: on the macro scale, things are bleak. Electing a fascist…