Blog

  • 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

  • Hello world!

    Welcome to WordPress. This is your first post. Edit or delete it, then start writing!