Skip to content

Instantly share code, notes, and snippets.

@SapphoSys
Last active March 26, 2025 00:10
Show Gist options
  • Select an option

  • Save SapphoSys/b0a3e348c8b50abf01e4d50ec1ec8686 to your computer and use it in GitHub Desktop.

Select an option

Save SapphoSys/b0a3e348c8b50abf01e4d50ec1ec8686 to your computer and use it in GitHub Desktop.
This is a very quick guide detailing how to set up a Bluesky PDS (Personal Data Server) using Docker Compose and Caddy.

Bluesky PDS using Docker Compose + Caddy

This is a very quick guide detailing how to set up a Bluesky PDS (Personal Data Server) using Docker Compose and Caddy.

For any questions, leave a comment on this GitHub Gist below, contact me on Bluesky at @sapphic.moe, or leave an email at contact@sapphic.moe

Note

This guide assumes your website is behind Cloudflare.

Prerequisites

  • Cloudflare account (which is free)
  • Cloudflare API token for Caddy ACME SSL (which is also free)
  • Docker (install guide here: link)

Getting started with Caddy

Let's create a folder in your home directory with the name services (ex: /home/chloe/services).

Inside services, create a folder with the name caddy.

In the services/caddy folder, create a file with the name Dockerfile and include the following. It'll be used to build Caddy with the Cloudflare DNS extension.

FROM caddy:builder-alpine AS builder
RUN xcaddy build \
  --with github.com/caddy-dns/cloudflare

FROM caddy:alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

In the services/caddy folder, create a file with the name compose.yml and include the following. It'll be used to start Caddy.

services:
  caddy:
    container_name: caddy
    build: .
    restart: unless-stopped
    env_file: .env
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_config:

networks:
  default:
    external: true
    name: web

Then (in the services/caddy folder), create a file with the name Caddyfile. This will be used to manage reverse proxying your services. (Replace domain.tld with your domain name)

{
  acme_dns cloudflare {env.CF_API_TOKEN}
}

*.pds.domain.tld, pds.domain.tld {
  reverse_proxy http://pds:3000
}

Obtaining a Cloudflare API token for Caddy

(Special thanks to CaddyBuilds for the guide below!)

To use the Cloudflare DNS challenge provider, you'll need to create an API token in your Cloudflare account. Follow these steps to create a token with the necessary permissions:

  1. Log in to Cloudflare:
  • Go to the Cloudflare dashboard at dash.cloudflare.com and log in with your account credentials.
  1. Navigate to API Tokens:
  • Click on your profile icon in the top right corner of the dashboard.
  • Select "My Profile" from the dropdown menu.
  • In the left sidebar, click on "API Tokens".
  1. Create a Custom Token:
  • Click the "Create Token" button.
  • Under the "Custom Token" section, click "Get started".
  1. Configure Token Permissions:
  • Give your token a name, such as "Caddy DNS-01 Challenge".
  • Set the permissions as follows:
    • Zone - Zone - Read: Allows the token to Read DNS Zones
    • Zone - DNS - Edit: Allows the token to edit DNS records, which is required for the DNS-01 challenge.
  1. Specify Account and Zone Resources:
  • Under "Zone Resources", set the following:
    • Include - All Zones: If you want the token to work with all your zones.
    • Include - Specific Zone: Select the specific zone(s) you want the token to have access to.
  1. Create and Store the Token:
  • Click the "Continue to summary" button.
  • Review your token settings and click "Create Token".
  • Copy the generated token.
  1. Set the Environment Variable:
  • Create a .env file in the services/caddy folder.
  • In the .env file, set the environment variable CF_API_TOKEN to the value of the token you just created.
CF_API_TOKEN=cf api token goes here

Setting up DNS in Cloudflare for your domain

Next, we're going to want to create some records on your domain.

  • Go to https://dash.cloudflare.com
  • Select your domain (in my case, it's domain.tld)
  • Go to the DNS section
  • Click the Add record button
  • Create an A record with the name pds, and the value of the IPv4 address is the IP address of your server. (you can put whatever in place of pds, like bsky, this is just an example)
  • Make sure the record is proxied and has an orange cloud by clicking on the Proxy status switch.
  • Click Save

We're going to add another record, but this time it's going to be a wildcard.

  • Click the Add record button
  • Create an A record with the name *.pds, and the value of the IPv4 address is the IP address of your server. (you can put whatever in place of *.pds, like *.bsky, this is just an example)
  • Make sure the record is NOT proxied this time around.
  • Click Save

If done correctly, this means you've:

  • created an A record with the name pds that is proxied, and is pointing to your server's IPv4 address. (example: pds.domain.tld)
  • created a wildcard A record with the name *.pds that is NOT proxied, and is pointing to your server's IPv4 address. (example: *.pds.domain.tld)

Starting Caddy

To start Caddy, head to the services/caddy folder in your home directory (in my case: /home/chloe/services/caddy), and execute the sudo docker compose up -d command. This will start the Caddy container in the background.

Optionally, to view logs, you can run sudo docker compose logs, or sudo docker compose logs -f to view in real time.

Configuring your Bluesky PDS

Now let's finally configure our PDS.

Create a pds folder in the services directory. (in my case: /home/chloe/services/pds)

Configuring the environment variables for your PDS

Let's configure the environment variables first.

Create a .env file in the services/pds folder and add the following:

PDS_HOSTNAME=
PDS_JWT_SECRET=
PDS_ADMIN_PASSWORD=
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=
PDS_DATA_DIRECTORY=/pds
PDS_BLOBSTORE_DISK_LOCATION=/pds/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
PDS_EMAIL_SMTP_URL=
PDS_EMAIL_FROM_ADDRESS=
LOG_ENABLED=true

For PDS_HOSTNAME, use the domain we've just set up in Cloudflare. (in this instance: PDS_HOSTNAME=pds.domain.tld)

(Replace domain.tld with your domain name.)

For PDS_JWT_SECRET, run this command: openssl rand --hex 16

For PDS_ADMIN_PASSWORD, you can use whatever here.

For PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX, run this command: openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32

For PDS_EMAIL_SMTP_URL, the syntax goes like this:

PDS_EMAIL_SMTP_URL=smtp://username@domain.com:passwordgoeshere@smtp.emailcompany.com:587

For PDS_EMAIL_FROM_ADDRESS, I just left it as username@domain.com.

You can follow Bluesky's SMTP guide for more info here: https://github.com/bluesky-social/pds?tab=readme-ov-file#setting-up-smtp

Configuring the Docker container for your PDS

Once you're done with the environment variables, create a file with the name compose.yml and include the following. It'll be used to start your PDS.

services:
  pds:
    container_name: pds
    image: ghcr.io/bluesky-social/pds:0.4
    networks:
      - web
    restart: unless-stopped
    volumes:
      - type: bind
        source: ./data
        target: /pds
    ports:
      - '7000:3000'
    env_file:
      - .env

networks:
  web:
    external: true

(The following was taken from https://cprimozic.net/notes/posts/notes-on-self-hosting-bluesky-pds-alongside-other-services and adapted for this gist. Thank you!)

By default, the PDS installation script creates the data directory at /pds. This is where the heart of your PDS lives. I moved it to the data folder in the same directory as the PDS' compose.yml file (/home/chloe/services/pds) for convenience purposes.

NOTE: Do not update the paths in your .env file accordingly. These paths are read from inside the container where it is mapped to /pds no matter where the directory is located on the host, so leave it as is.

Running your Bluesky PDS

Once all that is done, run sudo docker compose up in the services/pds folder. (in my case: /home/chloe/services/pds)

Your PDS should be up and running at https://pds.domain.tld!

If it is, you can now run sudo docker compose stop and then run sudo docker compose up -d to run the PDS container in the background.

@SapphoSys
Copy link
Author

Forgot to add the wildcard subdomain step during the DNS part! Added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment