self hosting a bluesky PDS thing

| self-hosting

i may be a loyalist to another decentralized social network but that doesn't mean i can simply ignore the prospect of hosting a server. so i reluctantly hosted a PDS, which in atproto/bluesky terms means, your way of hosting an instance, but it's not an instance, you can't do that on atproto which kind of sucks, but instead you host a "personal data server" which holds all your data and shit and then if you wanna leave bluesky and go elsewhere (once "elsewhere" exists) you can just take your data with you. it's an interesting concept i guess.

anyway their guide for this on their PDS repo read me honestly kind of sucks. like i was so confused. as polished as the bluesky UI is, this side of things really shows the jank of it all. but yeah it does work it's just weird about it. so here's how i did it.

prerequisites

of course you need a server, duh, if you self host you got this i'm assuming SSH knowledge. also you need to know how to use a reverse proxy. if you're doing this as your first self host thing and maybe your only one then you can probably get away with their default docker compose file which comes with caddy baked in, but i already use caddy bare metal to proxy my sites, so uh i had to remove that, which gave me problems. i'm assuming you'll be doing something similar - if you want to reverse proxy with nginx instead you can probably figure it out from what i'm doing with caddy or with the few other blog posts online on this same subject. also when you do your proxy stuff make sure you have ports 80 and 443 open. and if you're using caddy you don't need to worry about certs but if you use anything else i assume you know how to use certbot. lastly you need to install docker and really this'll be easier if you have basic docker knowledge trust me

DNS

so the read me makes this a little overcomplicated and so do other guides. the read me says you need a wildcard record AND certs and honestly those are always a fucking pain if you don't use a cloudflare domain so like, if it's just you on the instance you don't need it. and hell if you invite more people just manually make more records for them i guess like i do that on one of my sites. also some guides say you need a TXT record for some reason. yeah no all you need is an A record that points to your domain - you choose if it's a sub-domain or just the root of the domain which is the @ symbol for me on namecheap - and then put the IP for your server in there. let that register and boom you're done.

docker

fun part time. install docker and all that yada yada. ignore what the read me says on setup scripts and shit don't run those unless you want less control. you're gonna make a compose file for this right. they have a default but it includes 2 extra containers, one for caddy which we don't need, and then one for watchtower, which does automatic updates. and yeah you can keep watchtower if you want i guess but i prefer manual docker updates in case of breaking changes.

here's the compose file i made, based on that example of theirs:


services:
  pds:
    container_name: pds
    image: ghcr.io/bluesky-social/pds:0.4
    restart: unless-stopped
    volumes:
      - type: bind
        source: /home/kat/bsky/pds
        target: /pds
    ports:
      - '6010:3000'
    env_file:
      - /home/kat/bsky/pds.env

yeah it's literally just the same thing. make sure you change that 6010 port to whatever you want it to be for the proxy! also change those absolute paths to the paths you're using for your bluesky PDS data directory (/home/kat/bsky/pds up there), and the env file (/home/kat/bsky/pds.env). speaking of the env file!

you gotta put all the important stuff in here. i got confused on how to generate like keys and secrets and shit but i figured it out. here's an example env file with the stuff you need to fill in either blanked or given placeholders:


PDS_HOSTNAME=bsky.example.com
PDS_ADMIN_PASSWORD=
PDS_JWT_SECRET=
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=
PDS_BLOBSTORE_DISK_LOCATION=/pds/blocks
PDS_DATA_DIRECTORY=/pds
PDS_BLOB_UPLOAD_LIMIT=52428800
PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app
PDS_BSKY_APP_VIEW_URL=https://api.bsky.app
PDS_CRAWLERS=https://bsky.network
PDS_DID_PLC_URL=https://plc.directory
PDS_REPORT_SERVICE_DID=did:ar7c4by46qjdydhdevvrndac
PDS_REPORT_SERVICE_URL=https://mod.bsky.app
PDS_EMAIL_SMTP_URL=smtp://apikey:secret@mail.com:587
PDS_EMAIL_FROM_ADDRESS=example@example.com

let's go down the list. hostname is easy - put the domain you made a record for earlier. next is the admin password, do something secure, it can be anything i guess but be careful with special characters honestly i don't know if you need to escape those.

the JWT secret and PLC rotation key were confusing for me. thank you to this blog post and the person who wrote it for telling me how to generate the both of them which are just terminal one-liners that you grab the output of and drop into the env file.

for JWT secret: openssl rand --hex 16
for PLC rotation key: openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32

plop the outputs of both in their respective env file variables.

oh quick note - the PDS data directory says /pds right, DON'T CHANGE THAT! i mean like i don't know for sure but i'm half sure at least that since the compose file maps the PDS directory you made to /pds in the container, that means you should just leave that alone in the env file.

lastly, you need SMTP and a sender address in there to verify mail and change your password. i'm not gonna walk you through setting up a whole ass mail server because i did that once and became homicidal. instead i just use a mailjet account for like 99% of my mail needs. mail servers are actual hell ok.

for the SMTP URL scheme, this took me a bit, because the read me guide on this sucks, but here's the format:

smtp://API_KEY_AKA_USERNAME:SECRET_AKA_PASSWORD@smtp-relay-address-here.com:587

breaking this down:
smtp:// and 587 - so the guide says use smtps, and also port 465. yeah these did not work for me. unless your relay uses both of those, just substitute in smtp and 587.

API_KEY_AKA_USERNAME - mailjet calls the username an API key but others call it a username so yeah. plop that in there.

SECRET_AKA_PASSWORD - same thing, secret or password whatever they call it put it there. also MAKE SURE that the username/API and secret/password are separated by a colon!

smtp-relay-address-here.com - yeah just fill in your relay's address thing. they usually provide this when you log in, or at least mailjet does.

anyway we're done for now with docker stuff.

first run that won't work

ok well i don't know if it won't work fully but it won't work right. basically you gotta run bluesky's account creation shell script, but, bafflingly, they do not make it clear how to do that from a docker container. first you need to make a script called:
pdsadmin.sh
make that executable of course, then plop these contents in:


#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail

PDSADMIN_BASE_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin"

# Command to run.
COMMAND="${1:-help}"
shift || true

# Ensure the user is root, since it's required for most commands.
if [[ "${EUID}" -ne 0 ]]; then
  echo "ERROR: This script must be run as root"
  exit 1
fi

# Download the script, if it exists.
SCRIPT_URL="${PDSADMIN_BASE_URL}/${COMMAND}.sh"
SCRIPT_FILE="$(mktemp /tmp/pdsadmin.${COMMAND}.XXXXXX)"

if ! curl --fail --silent --show-error --location --output "${SCRIPT_FILE}" "${SCRIPT_URL}"; then
  echo "ERROR: ${COMMAND} not found"
  exit 2
fi

chmod +x "${SCRIPT_FILE}"
if "${SCRIPT_FILE}" "$@"; then
  rm --force "${SCRIPT_FILE}"
fi

i did not write that i stole it from the PDS github repo. but anyway here's how you actually use it from the container.

first run it with:
docker compose up -d
then run:
sudo ./pdsadmin.sh account create
do this from the directory you have the script in. so you DON'T have to exec it inside the container, it just somehow hooks in, but you have to have the container up and running to do this.

anyway when you run that it'll prompt you for a handle and email and stuff (this can be a personal email i just threw it my main one), give it that info, and it'll spit out some fun info that i can't copy paste here because i'm not re-running that script lol.

more importantly it'll have a line that mentions the DID which you need. copy paste that line in full and note it down. keep this noted for the next section. also note down the generated temporary password for your account.

caddy reverse proxy

oh fun! reverse proxies! you see these are like easy as hell for me usually but then fun stuff like this comes along and i'm like fighting for my life. i'll just drop my caddyfile config for my PDS here and explain it:


https://bsky.example.com, https://your-handle.bsky.example.com {
        reverse_proxy 192.168.1.123:6010

        @ws {
                header Connection *Upgrade*
                header Upgrade websocket
        }

        reverse_proxy @ws 192.168.1.123:6010

        handle /xrpc/* {
                        reverse_proxy 192.168.1.123:6010
        }

        handle /.well-known/atproto-did {
                respond "did:yourthinghere" 200
        }
}

yeah you can see i was confused as fuck here right. i'm pretty sure like all of this is unnecessary but i fought with it for a while and then didn't look at it for a week then tried again and this works so uh just go with it i guess. notice that i actually put in a second record for my handle, proxied up there as a sub-domain called your-handle. i don't know if you need to proxy this but you might want to in case.

replace "192.168.1.123" with your server's IPv4 address, and the port too if you changed that. and from the last section, that DID key you noted? replace the "did:yourthinghere" placeholder with your actual DID key.

it's getting epic

alright let's fucking go. you're almost there. run:
docker compose up -d
this will recreate the container with the new env value, and if that doesn't recreate then append "--force-recreate" to that command. from there, you can go to the bluesky site and if logged out it'll show you a sign-in button. click that, then it'll show you two sections: hosting provider and login credentials. click the hosting provider drop-down, and hit custom, and manually enter in the domain you made an A record for.

after that you can paste in the temporary password the script generated, and for username/email, well, either your handle or email. i went with email to be honest because i don't know how they want self hosted PDS usernames formatted in there.

and you're in! but you probably have issues

not epic part

so you're signed in and a nobody on there. you may notice that when you go to your profile, it says "Invalid Handle" with a little caution symbol. also it'll nag you to verify email. yeah that's annoying but the handle thing is weird. i couldn't figure this out for literally a week (and is what led to the weird web-socket fuckery in the caddyfile config) and left it alone. turns out, after combing through PDS repo github issues, one person noted that literally the root cause of this is some weird bug with the PDS where if you simply don't provide a bio and display name, it says it's invalid. FUCKING WEIRD!!!!!!!!

you may not have this error and it might just work fine for you but i had this error and it stumped me for a week and then when i came across that issue and adding both values fixed it i was absolutely dumbfounded. JANK!

also let's get to email verification, and maybe more importantly, setting a new password instead of the temporary one from the script. yeah you need SMTP server relay whatever shit set up for this. they don't let you change the password from their CLI script for some weird reason. annoying!

but yeah if you properly set up SMTP these should work fine from the bluesky website interface.

and uhhh.... you should be good now?

it's epic for real now

yeah i think you're done. set your profile pictures, names and shit, do whatever. uh i guess you can find me on there, @kat.bsky.girlonthemoon.xyz. i'm using it pretty sparingly and mostly making fun of bluesky because i'm a loser. um if you have questions about this guide just email me because there's no way in hell i'm putting comments on this blog (still gotta work on tags though....)


home | me