Skip to content

Installation

A self-contained Docker image for the Bluesky PDS (Personal Data Server) for use behind a reverse proxy such as Traefik.


Custom Domain

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

docker pull code.modernleft.org/gravityfargo/bluesky-pds:latest

Versioning and updates

The image tracks upstream bluesky-social/pds automatically. Each build resolves the newest upstream v* tag and publishes it as both latest and v<version>. A scheduled CI job checks upstream once a day and rebuilds only when a new release exists, so latest stays current on its own. A note is posted to Bluesky whenever a new version ships. Pulling the latest tag is enough to update. If a new upstream release has not shown up within a day, open an issue.

Don't use pdsadmin update. I have not tested this, and it may break things.

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.

Setup

Generate the secrets below, then set them either in your compose environment block or in a .env file loaded with env_file. See example.env for the .env layout.

# 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 runs as 1000:1000. Change this with the EUID and EGID environment variables. The legacy PUID and PGID names still work but print a deprecation notice.

Traefik

docker-compose.yml
# Traefik Proxy
services:
    bluesky-pds:
        image: code.modernleft.org/gravityfargo/bluesky-pds:latest
        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 this file BlueskyHeaders.

I think file configs are cleaner than having a billion labels. This is not required, but it's nice to have.

# middleware.yaml
http:
middlewares:
    BlueskyHeaders:
    headers:
        accessControlAllowMethods:
        - GET
        - OPTIONS
        - PUT
        - POST
        - DELETE
        accessControlAllowHeaders: "*"
        accessControlAllowOriginList: "*"
        addVaryHeader: true
        stsSeconds: 63072000

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:latest
        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

See Configuration for the full list of environment variables and their defaults.

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.

You can use a temporary alpine container for this.

Unraid does not come with the xxd command installed. There used to be a package called "NerdTools" that I used to use but it looks like it was removed. I do not recall if you can use docker run in Unraid, but these commands can be used. A quick google indicates windows users can use the docker cli.

# on host
docker run --rm -it alpine:latest sh

# in container
apk add xxd openssl
echo PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: $(openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail -c +8 | head -c 32 | xxd --plain --cols 32)

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 get JSON with the running PDS version, for example:

{ "version": "0.4.5009" }

The image also ships a Docker healthcheck against this endpoint, so docker ps shows the container as (healthy) once it is serving.

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"