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/@dap
— https://pr.inc.sh/@disc
— https://pr.inc.sh/@ende
— 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:<output-of-above-command>
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/dap.sh
— https://bsky.app/profile/disc.sh
— https://bsky.app/profile/ende.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? 🕸️