How to manually install Bluesky PDS without Docker

The Bluesky PDS can be installed on most systems with Node.js 18 or newer. This guide will focus on installation on a typical Linux system.

Install node.js with your distro’s package manager. If your distro’s version is too old, you can install the latest LTS version from the Nodesource repository.

After installing Node.js, you also need to install pnpm:

[sudo] corepack enable pnpm

Create a user and home directory for the PDS server:

[sudo] useradd -r -m -s /bin/bash pds

A folder will be created at /home/pds where everything can be stored.

Log in to the PDS user:

[sudo] su -l pds

Clone the PDS repository:

git clone https://github.com/bluesky-social/pds repo

This command will clone the repo to a folder named “repo” to avoid confusion.

Enter the repository and install the node modules:

cd repo/service
pnpm install --production --frozen-lockfile

Go back home and create the configuration file:

cd; nano pds.env

Paste the following contents:

PDS_HOSTNAME=
PDS_JWT_SECRET=
PDS_ADMIN_PASSWORD=
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=
PDS_DATA_DIRECTORY=/home/pds/data
PDS_BLOBSTORE_DISK_LOCATION=/home/pds/data/blocks
PDS_BLOB_UPLOAD_LIMIT=52428800
PDS_DID_PLC_URL=https://plc.directory
PDS_BSKY_APP_VIEW_URL=https://api.bsky.app
PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app
PDS_REPORT_SERVICE_URL=https://mod.bsky.app
PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac
PDS_CRAWLERS=https://bsky.network
LOG_ENABLED=true
PDS_PORT=3000
NODE_ENV=production

Create the missing directories:

mkdir -p /home/pds/data/blocks

You can use this command to generate a value for PDS_JWT_SECRET and PDS_ADMIN_PASSWORD. (don’t use the same value for both)

openssl rand --hex 16

Run this command to generate a value for PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX

openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32

Set the PDS_HOSTNAME to the domain name you will use for the PDS.

By default, the user handles will be subdomains of this domain name, but you can change this by adding the PDS_SERVICE_HANDLE_DOMAINS variable.

For example. if PDS_HOSTNAME=example.com, then users on the PDS will have handles like john.example.com.

But maybe you have a website on example.com so you want to set PDS_HOSTNAME=pds.example.com. Then your users will have handles like john.pds.example.com, and this will not work with Cloudflare.

In this case, set PDS_SERVICE_HANDLE_DOMAINS=.example.com. Note that the domain must start with a dot.

The service handle domain can be completely different than the PDS domain.


Create a systemd service file at /etc/systemd/system/pds.service:

[Unit]
Description=Bluesky Personal Data Server
After=network.target

[Service]
User=pds
Type=exec
EnvironmentFile=/home/pds/pds.env
WorkingDirectory=/home/pds/repo/service
ExecStart=node --enable-source-maps index.js

[Install]
WantedBy=multi-user.target

Enable the service

systemctl daemon-reload
systemctl enable --now pds

Check if it’s working

systemctl status pds
curl localhost:3000/xrpc/_health

After the PDS itself is running, you need to connect it to the web. You need an HTTPS server listening on port 443 to proxy to http://localhost:3000 (or whatever you set the PDS port to). The proxy server needs to support WebSockets, and it needs to provide the X-Forwarded-For header. Caddy does it perfectly with a single line config, but Nginx and Apache can do it too with more complicated configuration.

Caddy is also easier because it can automatically generate certificates for user handles, without using a wildcard certificate. The PDS is designed to work with Caddy’s on-demand TLS feature, so the caddy configuration looks like this.

{
	on_demand_tls {
		ask http://localhost:3000/tls-check
	}
}

*.example.com, example.com {
	tls {
		on_demand
	}
	reverse_proxy http://localhost:3000
}

To use a different server, you have to use an ACME client like Certbot with a DNS plugin for your DNS provider (i.e. Cloudflare, Porkbun) to get a wildcard certificate.

Todo: configuration examples for Apache, Nginx…

Alternatively, you can use Cloudflare’s “cloudflared” daemon to connect the PDS directly to the Cloudflare network. This way you do not need to port forward, open firewall ports, or get TLS certificates. But you cannot use domain names with more than three levels, i.e john.pds.example.com.


After the PDS is set up and available on the world wide web via an HTTPS reverse proxy server, it’s time to create the first account and request crawl from the Bluesky network.

Use the pdsadmin.sh script from the PDS repo:

cd /home/pds/repo
export PDS_ENV_FILE=/home/pds/pds.env
./pdsadmin.sh account create
./pdsadmin.sh request-crawl

Tip: Add export PDS_ENV_FILE=/home/pds/pds.env to ~/.profile so you don’t have to type it every time.

Log in to your new PDS account on the Bluesky app and it should be working.


To update, enter the repo folder and run git pull, then enter the source folder and run again:

pnpm install --production --frozen-lockfile

Then restart:

systemctl restart pds

To view PDS logs:

journalctl -fu pds

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *