The official Bluesky documentation on self-hosting a PDS is a little thin.
In addition, the installation instructions make several assumptions that don’t quite match my setup.
To get things running, I packaged up the PDS in a Dockerfile that can be deployed to services like Heroku, GCP Cloud Run, AWS Fargate, etc.
I happen to use Dokku, a “personal” Heroku clone, to host a variety of low-traffic services on a tiny cloud server. I wanted to install my Bluesky PDS as a Dokku app on that same server.
Here are my notes on how to do that.
There are a lot of open-source projects that promise a Heroku-like experience. Dokku is the first I’ve run across that (a) delivers near enough to that promise (b) is easy to install and use, and (c) seems to mostly “just work”.
Unlike Heroku, Dokku does not have a web-based UI. All interactions are through a reasonably well-documented CLI.
If you haven’t used Dokku before, see the well-written installation instructions.
Clone my bluesky-pds-docker
repository to your local machine:
git clone https://github.com/davepeck/bluesky-pds-docker.git bluesky-pds-docker
cd bluesky-pds-docker
Create a new Dokku app for your Bluesky PDS:
dokku apps:create bsky # or whatever name you'd like
This will also create a dokku
remote in your Git repository; at this point, so long as you’re running dokku
commands from the same directory, you don’t need to specify the app name.
Set up a persistent volume for your app:
dokku storage:ensure-directory bluesky-data
# I find it awkward that Dokku lets you ensure-directory with just a name,
# but seems to require you to mount it with a full path. Oh well.
dokku storage:mount /var/lib/dokku/data/storage/bluesky-data:/pds
Create a JWT secret for your PDS:
openssl rand --hex 16
You can also create an admin password this way too. (Or use your favorite password manager.)
Create a private key for DID PLC key rotation:
openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32
Set up your app’s environment variables:
dokku config:set \
# Replace these with your own values.
PDS_HOSTNAME=bsky.your-dokku-domain.com \
PDS_ADMIN_PASSWORD=your-admin-password \
PDS_JWT_SECRET=your-jwt-secret \
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=your-plc-rotation-key \
# If you want to verify your email address, you'll need to set these
PDS_EMAIL_FROM_ADDRESS=your-email-address \
PDS_EMAIL_SMTP_URL=smtp://[user]:[password]@[smtp-server]:[port] \
# Everything after this is default configuration.
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:plc:ar7c4by46qjdydhdevvrndac \
PDS_REPORT_SERVICE_URL=https://mod.bsky.app \
Now go ahead and deploy your app:
git push dokku main
Once the app is deployed, you’ll want to make sure your outbound port 80 is open. You can do this with a simple Dokku command:
dokku ports:add http:80:3000
Finally, let’s enable HTTPS. First, let’s install the Let’s Encrypt Dokku plugin:
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
Now enable Let’s Encrypt for your app:
dokku letsencrypt:enable
After a moment, your PDS should be up and running. You can access it at https://bsky.your-dokku-domain.com
. You should see a simple web page that says “This is an AT Protocol Personal Data Server (PDS)”.
Once your PDS is up and running, you’ll need to create a Bluesky account.
Log in to your PDS:
dokku run
Now create a new account:
./pdsadmin.sh account create
IMPORTANT. When creating this account, you’ll be asked for a new Bluesky handle. Annoyingly, when you first set up your handle, it has to be styled as a “subdomain” of your Bluesky server. Don’t worry, we will fix this, but for now, if your server is bsky.your-dokku-domain.com
, your handle should be something like <your-name>.bsky.your-dokku-domain.com
.
Assuming all goes well, you should see your account:
./pdsadmin.sh account list
Handle Email DID
<your-handle> <your-email> <some-plc-did-that-got-generated-for-you>
Okay, it’s time to sign in to Bluesky!
Go to https://bsky.app
and click “Sign in”.
Click “Hosting Provider”, select “Custom”, and enter your PDS domain name. Click “Done”.
Now, enter your current Bluesky handle (the one with the subdomain) and your PDS admin password. Click “Sign in”.
You’re in!
Now that you’re signed in, you can change your Bluesky handle if you like.
Under your user icon, click “Settings”. Under “Advanced”, click “Change handle”. Choose a new handle and then use one of the two provided methods (adding a DNS TXT record, or adding a .well-known file) to verify that you own the domain.
It can take 15-30 minutes for the change to take effect. Once it does, you’ll be able to sign in to Bluesky using your new handle and your PDS admin password.
You can also log in to your PDS to verify that your handle was successfully changed:
dokku run
./pdsadmin.sh account list
Have fun!
When you log in, Bluesky will nag you to validate your email.
If you’ve configured an SMTP server with the PDS_EMAIL_*
environment variables (see above), Bluesky will send you a validation email that contains a confirmation code.
There don’t appear to be any adverse side effects from leaving your email unvalidated, although Bluesky will continue to nag you about it from time to time.
While I focus on Dokku here, my approach could easily be adapted to other hosting environments, including Heroku, Google Cloud Run, or AWS Fargate. If you manage to get Bluesky running on one of these platforms, please let me know!
One useful thing to know is that Blueky’s PDS implementation supports both local storage (which is what I used) and S3-compatible storage. If you’re using Heroku or AWS, you probably want to store your data in S3. Just set the PDS_BLOBSTORE_DISK_LOCATION
environment variable to an s3://
URL and you should be good to go.