Document: WM-106 P. Webb Category: Socail 2026-10-26 Self-hosting Mastodon and Bluesky Abstract Remember re/thinking Twitter handles for your various projects? It’s 2026, I’m not doing all that for a shit platform. I will spend hours wrangling social I control though. Body A few weeks ago[1] I decided to look into self-hosting a Mastodon server for my various projects and Sang[2] told me about GoToSocial[3], the software he uses to self-host his own. For Bluesky I used the official PDS software[4]. ## GoToSocial I decided to put GoToSocial on the same server I’m hosting ~15 other sites because it’s beefy enough. This is how you create accounts: ``` sudo -u gotosocial ./gotosocial --config-path ./config.yaml admin account create --username friend --email friend@example.org --password 'p@$$w0rd!with`odd*chars' ``` After the FIRST account you make (this is your admin, right?), you’ll have to promote it to `admin`: ``` sudo -u gotosocial ./gotosocial --config-path ./config.yaml admin account promote --username you ``` I installed GoToSocial at `pr.inc.sh`. For some reason, the directory isn’t showing all the accounts I created. Here are the direct links to the accounts I made thus far: — [https://pr.inc.sh/@inc](https://pr.inc.sh/@inc) — [https://pr.inc.sh/@dap](https://pr.inc.sh/@dap) — [https://pr.inc.sh/@disc](https://pr.inc.sh/@disc) — [https://pr.inc.sh/@ende](https://pr.inc.sh/@ende) — [https://pr.inc.sh/@eol](https://pr.inc.sh/@eol) ## Bluesky Setting up a PDS on an existing server meant I had to make several adjustments to their guide. I’m already using Caddy so my `/pds/compose.yaml` didn’t need that. ``` services: pds: container_name: pds env_file: - /pds/pds.env image: ghcr.io/bluesky-social/pds:0.4 ports: - "127.0.0.1:3000:3000" restart: unless-stopped volumes: - type: bind source: /pds target: /pds watchtower: command: pds container_name: watchtower environment: WATCHTOWER_CLEANUP: "true" WATCHTOWER_SCHEDULE: "0 0 4 * * *" image: ghcr.io/nicholas-fedor/watchtower:latest restart: unless-stopped volumes: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock ``` (I changed my ports because `3000` is such a common port, I didn’t want interference with something else on my server.) This is how you create accounts: ``` docker exec pds goat pds admin account create --email friend+handle@example.org --handle handle.pds.domain --pds-host https://pds.domain --password 'p@$$w0rd!with`odd*chars' ``` When you run this, you’ll see a `DID` of the created account. The cool thing about Bluesky is being able to use your domain as your handle. Add a `TXT` record to your domain with: ``` did=did:plc: ``` and your account will be ready to switch to the domain handle. My PDS is at `at.inc.sh`. Here’s the Bluesky variants of my Mastodon accounts: — [https://bsky.app/profile/inc.sh](https://bsky.app/profile/inc.sh) — [https://bsky.app/profile/dap.sh](https://bsky.app/profile/dap.sh) — [https://bsky.app/profile/disc.sh](https://bsky.app/profile/disc.sh) — [https://bsky.app/profile/ende.sh](https://bsky.app/profile/ende.sh) — [https://bsky.app/profile/eol.sh](https://bsky.app/profile/eol.sh) I wasn’t able to get email verification working but the way to get around that is to install `sqlite3` on your server. ``` apt install -y sqlite3 ``` List the `*.sqlite` files your PDS generates: ``` ls /pds/*.sqlite ``` You should see: ``` /pds/account.sqlite /pds/did_cache.sqlite /pds/sequencer.sqlite ``` Finally, initiate email verification in the Bluesky app and run this in your terminal: ``` sudo sqlite3 /pds/account.sqlite "SELECT did, purpose, token FROM email_token;" ``` You’ll see something like: ``` did:plc:abcdefghijklmnopqrstuvwx|confirm_email|ABCDE-FGHIJ ``` That last bit (`ABCDE-FGHIJ`) is the code Bluesky is looking for. In the UI, click the button that asks for a code and paste it in. This is your own PDS so if anything goes wrong, why do you need to be emailed? You’d know if something fell over…probably. If your setup is like mine, do NOT run `pdsadmin update`. It replaces `/pds/compose.yaml` with the stock file and sets up its own Caddy situation. I should probably put this stuff in 1Password anyway, just in case. This is great because now I won’t feel like I’m spamming people with all my project updates and musings and ramblings. If you opt-in to follow my various accounts, thanks! You can enjoy the deluge of words at your leisure. There’s still the matter of Discord and uhh, meh? I use it but the idea of creating a server for every project sounds insane. Maybe I could just create the one and have a channel for each project? Meh? 🕸️ References [1] [2] [3] [4]