Custom Domain

You do not need to setup a PDS to have a custom domain in your username. Bluesky provides documentation for DNS setup.

Official bluesky-pds projectGitHub - bluesky-social/pds
Image on ForgejoForgejo
Image on DockerhubDockerhub
Image SourceForgejo
Issue TrackerGitHub - gravityfargo/bluesky-pds-docker
docker pull code.modernleft.org/gravityfargo/bluesky-pds:v0.4.74

A self-contained Docker image for the Bluesky PDS (Personal Data Server) for use with Traefik.

It is required to run the instance behind a proxy (like Traefik) to generate SSL certificates. This will not work otherwise. The standard pds install includes caddy to handle this. A wildcard DNS assignment along with a wildcard SSL certificate is required.

This is not intended for production, and I am not responsible for any data loss or security issues. This is a personal project, and I am not affiliated with Bluesky.


Setup

Generate secrets and add them to .env file. See example.env as an example.

# Generate secret environment variables
echo PDS_ADMIN_PASSWORD: $(openssl rand --hex 16)
echo PDS_JWT_SECRET: $(openssl rand --hex 16)
echo 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)

Compose File

By default, the image uses 1000:1000 as the UID:GID for the user. This can be changed by setting the PUID and PGID environment variables.

Traefik

docker-compose.yml
# Traefik Proxy
services:
  bluesky-pds:
    image: code.modernleft.org/gravityfargo/bluesky-pds:v0.4.74
    networks:
      - proxy
    environment:
      PDS_JWT_SECRET: ...
      PDS_ADMIN_PASSWORD: ...
      PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: ...
      PDS_HOSTNAME: example.com
      # PDS_EMAIL_SMTP_URL:
      # PDS_EMAIL_FROM_ADDRESS:
      EUID: 1000
      EGID: 1000
    volumes:
      - ./bluesky-pds:/pds
    labels:
      traefik.enable: "true"
      traefik.http.routers.bluesky-pds-insecure.entrypoints: http
      traefik.http.routers.bluesky-pds-insecure.rule: HostRegexp(`^.+\.example\.com$`) || Host(`example.com`)
      # traefik.http.routers.bluesky-pds-insecure.middlewares: BlueskyHeaders@file
      traefik.http.routers.bluesky-pds-secure.entrypoints: https
      traefik.http.routers.bluesky-pds-secure.rule: HostRegexp(`^.+\.example\.com$`) || Host(`example.com`)
      traefik.http.routers.bluesky-pds-secure.tls: "true"
      traefik.http.services.bluesky-pds.loadbalancer.server.scheme: http
      traefik.http.services.bluesky-pds.loadbalancer.server.port: 3000
      # traefik.http.routers.bluesky-pds-secure.middlewares: BlueskyHeaders@file

Optionally, you can use the BlueskyHeaders middle ware to set headers.

Standalone

I do not run this, but it should be possible. You must setup SSL for it to connect to the Bluesky network. I have not tested this.

docker-compose.yml
# Standalone, you'll need to add a proxy in front of this with SSL.
services:
  bluesky-pds:
    container_name: bluesky-pds
    image: code.modernleft.org/gravityfargo/bluesky-pds:v0.4.74
    environment:
      PDS_JWT_SECRET: ...
      PDS_ADMIN_PASSWORD: ...
      PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: ...
      PDS_HOSTNAME: example.com
      # PDS_EMAIL_SMTP_URL:
      # PDS_EMAIL_FROM_ADDRESS:
      EUID: 1000
      EGID: 1000
    volumes:
      - ./bluesky-pds:/pds

Environment Variables

Full list of additional Environment Variables provided by Bluesky upstream can be found in the packages/pds/src/config/env.ts

VariableDescriptionDefaultRequired
PDS_JWT_SECRETJWT secret for the PDS server""*
PDS_ADMIN_PASSWORDPassword for the admin account""*
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEXPrivate key for the PDS server""*
PDS_HOSTNAMEHostname for the PDS server""*
PDS_EMAIL_SMTP_URLSMTP URL and authentication for email""
PDS_EMAIL_FROM_ADDRESSEmail address for email notifications""
EUIDUser ID for the container1000
EGIDGroup ID for the container1000
LOG_ENABLEDIf the stdout will be shown in the consoletrue
LOG_DESTINATIONWhere the logs will be sentstdout
LOG_LEVELThe level of logginginfo

Unraid

While I do not use Unraid, a user on Reddit has added this project to the Unraid App store. I won’t be providing any meaningful support it.

Unraid does not include the xxd package. You’ll need another Linux machine for that. I made some instructions for using a temporary alpine container.

Check your work

This is just a reiteration of Verifying that your PDS is online and accessible from the pds docs.

If you go to https://example.com/xrpc/_health you should be greeted with the JSON

{ "version": "0.4.74" }

To check that WebSockets are working use the go package wsdump or go to PieSoket’s online websocket tester

wsdump "wss://example.com/xrpc/com.atproto.sync.subscribeRepos?cursor=0"

Running Commands

Nothing has changed in this department other than not needing sudo. The commands are the same as those for the upstream project. Such as

docker exec -it bluesky-pds bash
pdsadmin account create
pdsadmin create-invite-code

Updating

Don’t use pdsadmin update. I have not tested this, and it may break things. Submit an issue to my repository requesting an update, and I will release a new image version.

Networking

Container Hostname

If you set the container hostname to the domain of your pds server,

services:
  bluesky-pds:
    hostname: example.com

you will get the error curl: (7) Failed to connect to example.com port 443 after 0 ms: Couldn't connect to server. You have two options.

  1. Do not set the hostname.
  2. Add the extra_hosts: section to your compose file to act as an entry to /etc/hosts that manually points the FQDN to your external IP. See the Cloudflare DNS section for an example.

Cloudflare DNS

Cloudflare

Cloudflare

I do not use their services after their many controversies. Theo - t3.gg has a good video on the topic. I wont be providing support for any issues that may arise. If you are a CF user and encounter issues, disable DNS proxy and restart the service.

Link to original

This container does not work well with Cloudflare. I won’t provide support. However, here are some notes. In the Cloudflare control panel, you need to enable WebSockets. You also need to set

services:
  bluesky-pds:
    image: forgejo.gravityfargo.dev/gravityfargo/bluesky-pds:latest
    extra_hosts:
      - "example.com:0.0.0.0" # must be the external IP to bypass cloudflare.

I stopped using Cloudflare around the same time I was messing with this. So I don’t have any more information than this.

A user on reddit commented with a setup for using cloudflared if that helps.

FAQ

Why does command pdsadmin ... give me curl: (7) Failed to connect?

The pds server is unable to resolve itself. See the Networking section for more information.

I keep getting “/pds/pds.env: line N: $N: unbound variable”

You have not set all of the required environment variables.

Error: Resource URL must use the https scheme

PDS_HOSTNAME is not set.

”Invalid Handle”