ben blogs

wherever you go, there you are

Bluesky PDS Without Docker

Here’s how I got a self-hosted PDS (personal data server) running without docker.

This can be useful if you want to run the PDS on an existing machine or just don’t like docker. I came up with these steps by emulating what the installer script does.

My setup uses nginx and a wildcard TLS cert for my PDS domain.

Get the code

Clone the PDS repo

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

Set up nginx

I use certbot to issue wildcard certs for my domains. See my wildcard cert script here. Note that you will need to set up credentials for your nameservers. I’m not aware of a way in nginx to issue certs on-demand like the example caddy config does.

Here’s my nginx config for the PDS.

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

server {
  listen 80;
  server_name hellthread.pro *.hellthread.pro;
  return 302 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name hellthread.pro *.hellthread.pro;
  ssl_certificate /etc/letsencrypt/live/hellthread.pro/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/hellthread.pro/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  client_max_body_size 500M;

  location / {
    include proxy_params;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_pass http://127.0.0.1:3002;
  }
  location /blob/ {
    # this adds a pretty url for blobs
    # in the format of https://pds/blob/did/cid
    rewrite ^/blob/(.*)/(.*)$ /xrpc/com.atproto.sync.getBlob?did=$1&cid=$2 last;
  }
}

Configure your .env file

Let’s create the service/.env file.

With the below example, adjust the top 6 options, filling in your domain, SMTP credentials, and generating keys with the following commands:

Use this for the JWT_SECRET:

openssl rand --hex 16

Use this twice to generate the admin password and rotation key:

openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32
PDS_HOSTNAME="your domain here"
PDS_JWT_SECRET="generated secret"
PDS_ADMIN_PASSWORD="generated key"
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="another generated key"
PDS_EMAIL_SMTP_URL="smtp://user:password@mail.example.com"
PDS_EMAIL_FROM_ADDRESS="pds@example.com"

PDS_DATA_DIRECTORY=./data
PDS_BLOBSTORE_DISK_LOCATION=./data/blocks
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
NODE_ENV=production
PDS_PORT=3002

Run the PDS

Be sure to install the dependencies:

cd service
pnpm install --production --frozen-lockfile
mkdir -p data/blocks

This is the systemd setup I use to run the PDS. Add your own user unit with the following steps:

mkdir -p ~/.config/systemd/user
$EDITOR ~/.config/systemd/user/pds.service
# copy in the example below and adjust as needed
systemctl --user daemon-reload
systemctl --user enable --now pds

~/.config/systemd/user/pds.service:

[Unit]
Description=atproto personal data server

[Service]
WorkingDirectory=/home/ben/workspace/pds/service
ExecStart=/usr/bin/node --enable-source-maps index.js
Restart=on-failure
EnvironmentFile=/home/ben/workspace/pds/service/.env

[Install]
WantedBy=default.target

View the logs from journalctl like this:

journalctl --user --output=cat --follow --unit pds | jq

You can run the pdsadmin commands by setting the PDS_ENV_FILE variable like this:

PDS_ENV_FILE=../service/.env bash account.sh list
Handle              Email               DID
ben.hellthread.pro  ben@hellthread.pro  did:plc:g5isluhi3wkw557ucarjgtgy

Update

To update your PDS, use git pull in the directory you cloned it in. Then update dependencies in the service subdirectory and restart the unit:

cd pds
git pull
cd service
pnpm install --production --frozen-lockfile
systemctl --user restart pds

Likes, Bookmarks, and Reposts

  • Nick Gerakines

29 responses

  1. […] an invite code for a PDS. I’ve got a guide for setting one up without using docker if you want to run your own. I’m happy to get you an […]

  2. […] ran into a couple snags while moving my Bluesky account to my self-hosted PDS. Wanted to gather these in one spot for future […]

  3. ben

    testing inline links like to my website

    that is pretty nice, much better control over the facets that way

  4. ⚙️ /usr/libexec/ccoremapd

    oh, wow. that’s so cool!! 😀

  5. ⚙️ /usr/libexec/ccoremapd

    oh, wow. that’s so cool!! 😀

  6. ⚙️ /usr/libexec/ccoremapd

    it, works! thank you!! 😀

    although, i have to use node v20 (v18 and v22 does not work) in order to run it properly.

  7. ⚙️ /usr/libexec/ccoremapd

    it, works! thank you!! 😀

    although, i have to use node v20 (v18 and v22 does not work) in order to run it properly.

  8. ben

    I found your post because bridgy fed detected it as a webmention for the original post and it came in to my wordpress comments on my site 😎

  9. ben

    I found your post because bridgy fed detected it as a webmention for the original post and it came in to my wordpress comments on my site 😎

  10. ben

    make sure you have EnvironmentFile= set in the service

  11. ben

    make sure you have EnvironmentFile= set in the service

  12. ben

    oh for this one you need the –env-file flag for node, the pds doesn’t have any logic to load that automatically

  13. ben

    oh for this one you need the –env-file flag for node, the pds doesn’t have any logic to load that automatically

  14. ⚙️ /usr/libexec/ccoremapd

    oh, hi! 😀

    i use the same value as you, tho. but, all i got is the same result. previously, i’ve ran it successfully but now it’s not.

    hmmm… might have to nuke the machine and start from scratch if i have to.

    anyway, here’s my dotenv config and some errors that i’ve got.

  15. ⚙️ /usr/libexec/ccoremapd

    oh, hi! 😀

    i use the same value as you, tho. but, all i got is the same result. previously, i’ve ran it successfully but now it’s not.

    hmmm… might have to nuke the machine and start from scratch if i have to.

    anyway, here’s my dotenv config and some errors that i’ve got.

  16. ⚙️ /usr/libexec/ccoremapd

    by the way, i’ve executed it without systemd first in order to diagnose the problem. as it also get the same result and no detailed logs have been outputted on systemd, tho.

  17. ⚙️ /usr/libexec/ccoremapd

    by the way, i’ve executed it without systemd first in order to diagnose the problem. as it also get the same result and no detailed logs have been outputted on systemd, tho.

  18. ben

    for mine i’m using local disk storage so the value is ‘./data/blocks’

  19. ben

    for mine i’m using local disk storage so the value is ‘./data/blocks’

  20. ben

    what value do you have for PDS_BLOBSTORE_DISK_LOCATION in your .env

  21. ben

    what value do you have for PDS_BLOBSTORE_DISK_LOCATION in your .env

  22. ben

    holler if you need any help with the non-docker setup, i’ve also added some clarification on there

  23. Ricardo

    pas encore déployé, je me familiarise d’abord avec la chose 😉

  24. David

    Alors ça fonctionne bien ?

Leave a Reply

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

To respond on your own website, make a post that contains the link to this post and enter the URL of your response. Want to update or remove your response? Update or delete your post and re-enter your post’s URL again. (Learn More)