# bluesky-pds * **Category**: Apps * **Status**: 0 * **Image**: ghcr.io/bluesky-social/pds * **Healthcheck**: Yes * **Backups**: No * **Email**: No * **Tests**: No * **SSO**: No ## Quickstart 1. `abra app new bluesky-pds` (do **not** use `--secrets` yet, see below) 2. Generate secrets: The JWT secret and admin password can be generated automatically: ```bash abra app secret generate YOURAPPDOMAIN pds_jwt_secret v1 abra app secret generate YOURAPPDOMAIN pds_admin_password v1 ``` The PLC rotation key is a secp256k1 private key and must be generated manually: ```bash openssl ecparam --name secp256k1 --genkey --noout --outform DER | \ tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32 ``` Then store it as a secret: ```bash abra app secret insert YOURAPPDOMAIN pds_plc_rotation_key v1 ``` 3. `abra app deploy YOURAPPDOMAIN` Verify the PDS is running: `curl https://YOURAPPDOMAIN/xrpc/_health` ## Account management Create an account on your PDS (use the admin password you stored during secret generation): ```bash abra app run YOURAPPDOMAIN app -- \ goat pds admin account create \ --admin-password YOUR_ADMIN_PASSWORD \ --handle user.YOURAPPDOMAIN \ --email user@example.com \ --password yourpassword ``` Create an invite code: ```bash abra app run YOURAPPDOMAIN app -- \ goat pds admin create-invites \ --admin-password YOUR_ADMIN_PASSWORD ``` ## Usage Once you've created an account (see above), you can log in with any ATProto-compatible client: 1. Open [bsky.app](https://bsky.app) (or another client like Graysky, Sky.app, etc.) 2. On the login screen, tap **Hosting provider** (or **Custom PDS** depending on the client) 3. Enter your PDS hostname: `YOURAPPDOMAIN` 4. Log in with the handle and password you used when creating the account Your handle will be `user.YOURAPPDOMAIN` by default (a subdomain handle). You can switch to a custom domain handle — see **Handle configuration** below. ## Handle configuration User handles on a PDS can work in two ways: 1. **Subdomain handles** (e.g. `user.pds.example.com`): The default. Requires a wildcard DNS record (`*.pds.example.com`) pointing to your server. TLS is handled automatically by the Caddy sidecar (see below). 2. **Domain handles** (e.g. `user.com`): Users can use their own domain as a handle by adding a DNS TXT record at `_atproto.user.com` with the value `did=did:plc:`. This works without any additional server configuration. ## DNS setup At minimum, create an A record pointing your PDS domain to your server: ``` pds.example.com A ``` For subdomain handles, also add a wildcard record: ``` *.pds.example.com A ``` [`abra`]: https://git.coopcloud.tech/coop-cloud/abra [`coop-cloud/traefik`]: https://git.coopcloud.tech/coop-cloud/traefik ## TLS architecture (Caddy sidecar) This recipe uses a **Caddy sidecar** for TLS instead of letting Traefik terminate TLS directly. This is needed because Bluesky subdomain handles require TLS certificates for each `user.pds.example.com` subdomain, and Traefik cannot issue on-demand per-subdomain certificates. The architecture: 1. **Traefik** receives TLS connections on port 443 and does **TCP passthrough** (no TLS termination) for traffic matching `DOMAIN` and `*.DOMAIN`, forwarding the raw TLS stream to Caddy. 2. **Caddy** terminates TLS using **on-demand certificates** — it automatically obtains a Let's Encrypt certificate for each subdomain the first time a connection arrives, using the TLS-ALPN-01 challenge. 3. **Caddy** reverse proxies the decrypted HTTP traffic to the PDS on port 3000. This matches how the [upstream PDS](https://github.com/bluesky-social/pds) is designed to work (it ships with Caddy), adapted for Co-op Cloud's Traefik-based routing. The PDS exposes a `/tls-check` endpoint that Caddy consults before issuing a certificate, preventing abuse. **Note:** The first request to a new subdomain handle may take 10-30 seconds while Caddy obtains the TLS certificate from Let's Encrypt. Subsequent requests are instant. No changes to the Traefik recipe are needed — the TCP passthrough is configured entirely via deploy labels on the Caddy service in this recipe's `compose.yml`. ## About A [Bluesky PDS](https://github.com/bluesky-social/pds) (Personal Data Server) is a self-hosted server for ATProto. This is a co-op cloud recipe for a PDS as implemented by bluesky, although other pds implementations exist such as [rsky-pds](https://github.com/blacksky-algorithms/rsky/tree/main/rsky-pds). # ❃ recipe maintained by @notplants