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
This guide assumes your website is behind Cloudflare.
- Cloudflare account (which is free)
- Cloudflare API token for Caddy ACME SSL (which is also free)
- Docker (install guide here: link)
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/caddyIn 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: webThen (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
}
(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:
- Log in to Cloudflare:
- Go to the Cloudflare dashboard at dash.cloudflare.com and log in with your account credentials.
- 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".
- Create a Custom Token:
- Click the "Create Token" button.
- Under the "Custom Token" section, click "Get started".
- 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.
- 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.
- Create and Store the Token:
- Click the "Continue to summary" button.
- Review your token settings and click "Create Token".
- Copy the generated token.
- Set the Environment Variable:
- Create a
.envfile in theservices/caddyfolder. - In the
.envfile, set the environment variableCF_API_TOKENto the value of the token you just created.
CF_API_TOKEN=cf api token goes hereNext, 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 ofpds, likebsky, 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
pdsthat is proxied, and is pointing to your server's IPv4 address. (example:pds.domain.tld) - created a wildcard A record with the name
*.pdsthat is NOT proxied, and is pointing to your server's IPv4 address. (example:*.pds.domain.tld)
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.
Now let's finally configure our PDS.
Create a pds folder in the services directory. (in my case: /home/chloe/services/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=trueFor 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:587For 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
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.
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.
Forgot to add the wildcard subdomain step during the DNS part! Added.