docker pull atcr.io/rtw.run/pds:latest
pds
ATProto Personal Data Server (the Bluesky reference PDS, @atproto/pds) for rtw.run.
Hosts ATProto accounts on pds.rtw.run (handles like user.pds.rtw.run) and federates publicly via the bsky.network relay. Serves the ATProto XRPC API on port 3000.
The PDS reads all configuration from PDS_* environment variables. It never generates or persists secrets and this image ships none; the container runs as uid 1000 and persists all account data under /data (mount a volume there).
Secrets
Three secrets are required in production (set via systemd EnvironmentFile= or -e, never baked into the image):
| Var | Example generation |
|---|---|
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 |
PDS_JWT_SECRET |
openssl rand --hex 16 |
PDS_ADMIN_PASSWORD |
openssl rand --hex 16 |
ENV=development generates throwaway secrets at startup and logs a warning — the instance is ephemeral and its data is not recoverable. Production (ENV unset) fails fast if any secret is missing.
Running the container
Multiarch image (amd64 + arm64): atcr.io/rtw.run/pds:latest
mkdir -p /srv/rtw/pds
podman run -d --name pds --userns=keep-id \
-p 127.0.0.1:3000:3000 \
-v /srv/rtw/pds:/data \
-e PDS_HOSTNAME=pds.rtw.run \
-e PDS_CRAWLERS=https://bsky.network \
-e PDS_INVITE_REQUIRED=true \
-e PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=<64-hex> \
-e PDS_JWT_SECRET=<secret> \
-e PDS_ADMIN_PASSWORD=<secret> \
atcr.io/rtw.run/pds:latest
Environment
| Var | Default | Meaning |
|---|---|---|
PDS_HOSTNAME |
localhost (dev) |
Fully-qualified hostname; required in production |
PDS_DATA_DIRECTORY |
/data |
Data dir on the volume (sqlite DBs) |
PDS_BLOBSTORE_DISK_LOCATION |
$PDS_DATA_DIRECTORY/blobs |
Uploaded blobs (disk store) |
PDS_PORT |
3000 |
Listening port |
PDS_CRAWLERS |
unset | e.g. https://bsky.network for public federation |
PDS_INVITE_REQUIRED |
unset | true to require an invite code to sign up |
PDS_SERVICE_DID |
did:web:<hostname> |
Service DID override |
PDS_DEV_MODE |
false |
true relaxes the https-only URL requirement and disables SSRF protection (auto-set in dev) |
PDS_SERVICE_NAME / PDS_SERVICE_DESC / PDS_BRANDING_* |
– | Branding shown on the web UI |
Admin access is HTTP basic auth with user admin and PDS_ADMIN_PASSWORD (e.g. for com.atproto.server.createInviteCode).
Health check: curl http://localhost:3000/xrpc/_health.
Source: bluesky-social/atproto · Docs: https://atproto.com/guides/self-hosting