Running a PDS
A Colibri community is a set of gated spaces, all authored by the
community’s own DID, plus a personal space for every member (see
Spaces and Communities).
Those spaces have to live on a real PDS, and it has to run the spaces alpha
image, ghcr.io/bluesky-social/atproto:pds-spaces-alpha, because
com.atproto.space and com.atproto.simplespace don’t exist on a stable PDS
release.
PDS_URL in .env is one of the six variables the AppView refuses to boot
without. PDS_ADMIN_PASSWORD is separate and optional: without it the AppView
still serves reads, and creating or repairing a community’s credentials fails.
The local overlay
Section titled “The local overlay”The AppView repository ships a compose overlay that adds a spaces-alpha PDS and its own private PLC directory, so nothing a development community mints reaches the public PLC directory:
docker compose -f docker-compose.yml -f docker-compose.pds.yml upThe same PDS and PLC pair, run ephemeral, back pnpm test:integration:
see docker-compose.integration.yml.
The PDS image only runs on amd64
Section titled “The PDS image only runs on amd64”ghcr.io/bluesky-social/atproto:pds-spaces-alpha is published for amd64
only, so the overlay pins platform: linux/amd64 rather than letting Docker
infer it. On an arm64 host, such as an Apple Silicon Mac, that means the PDS
runs under emulation. It works, but expect it to be the slowest part of your
local stack.
The PLC directory is built from source
Section titled “The PLC directory is built from source”There is no current PLC directory image published anywhere. Every
ghcr.io/bluesky-social/atproto:plc-* tag on any registry dates from 2023 and
predates the plc_operation format the spaces-alpha PDS emits, so pointing it
at one of those fails every account creation with Not a valid operation.
docker/plc in the AppView repository builds
@did-plc/server from
source instead, the same package atproto’s own dev-env runs against, so this
is what upstream tests with even though its published version hasn’t moved
since 2023.
That directory keeps no state by default: with no DATABASE_URL its operation
log lives in memory, so it starts empty and forgets everything when the
container stops. The plain dev overlay gives it a DATABASE_URL instead, so a
restart doesn’t orphan every community whose data the PDS still holds. Either
way it’s a private directory: a DID minted there doesn’t exist anywhere else,
so nothing outside your own stack can resolve it.
Configuration the overlay sets for you
Section titled “Configuration the overlay sets for you”docker-compose.pds.yml points the AppView at the local PDS and PLC and picks
a test handle domain, so you don’t set these yourself:
PDS_URL=http://pds:3001PLC_URL=http://plc:2582COMMUNITY_HANDLE_DOMAIN=testIt also sets JETSTREAM_ENABLED=false, since the AppView only consumes
Jetstream for identity and account events on the wider network, and a
throwaway local stack has no need for that traffic.
The PDS itself needs PDS_JWT_SECRET and PDS_PLC_ROTATION_KEY in your
.env, both development-only variables read by the overlay rather than the
AppView. .env.example carries a working default for the JWT secret and the
openssl command for the rotation key:
openssl ecparam -name secp256k1 -genkey -noout -outform DER \ | tail -c +8 | head -c 32 | xxd -p -c 32The PDS runs on the port it publishes, 3001, because it advertises
http://localhost:3001 in every DID document it writes, and anything outside
the Compose network resolves space hosts out of those documents.
Verifying
Section titled “Verifying”# the PDS is upcurl http://127.0.0.1:3001/xrpc/_health
# so is its PLC directorycurl http://127.0.0.1:2582/_healthSee also
Section titled “See also”- Spaces: what a space is and how access to one is decided.
- Communities: the six spaces a community is made of.
- Self-Hosting: running a PDS for real users, with real TLS and DNS.