Files
bluesky-pds/README.md
2026-02-23 13:41:18 +00:00

153 lines
4.9 KiB
Markdown

# bluesky-pds
<!-- metadata -->
* **Category**: Apps
* **Status**: 0
* **Image**: ghcr.io/bluesky-social/pds
* **Healthcheck**: Yes
* **Backups**: No
* **Email**: No
* **Tests**: No
* **SSO**: No
<!-- endmetadata -->
## Quickstart
0. setup a server with [abra](https://git.coopcloud.tech/toolshed/abra) and deploy [coop-cloud/traefik](https://git.coopcloud.tech/coop-cloud/traefik)
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 <THE_KEY_HEX>
```
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:<their-did>`. 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 <server-ip>
```
For subdomain handles, also add a wildcard record:
```
*.pds.example.com A <server-ip>
```
[`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 [cocoon](https://tangled.org/hailey.at/cocoon), [tranquil-pds](https://tangled.org/tranquil.farm/tranquil-pds), [pegasus](https://tangled.org/futur.blue/pegasus) and [rsky-pds](https://github.com/blacksky-algorithms/rsky/tree/main/rsky-pds).
# ❃
recipe maintained by @notplants