README.md

ATProto PDS #

Self-hosted Bluesky/ATProto PDS + pds-gatekeeper, as Terraform + Ansible.

  • Terraform provisions a dedicated DigitalOcean droplet (devict-pds).
  • Ansible installs Docker + renders the PDS compose stack.
  • Secrets live in /pds/pds.env (mode 0600). The three crypto secrets — PDS_JWT_SECRET, PDS_ADMIN_PASSWORD, PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX — are generated on first run and kept on disk; back them up (they're in 1Password). For a new deployment (or single-secret rotation), generate them with just gen-secrets (see scripts/generate-pds-secrets.sh) and store the output in 1Password before first apply.
  • Gatekeeper is added as a sidecar service; enable features via role vars.

Prerequisites #

Create atprotopds/.env.local.op (1Password-backed, not committed) with:

TF_VAR_do_token=op://Private/env-devict-infra/DIGITALOCEAN_TOKEN
TF_VAR_ssh_public_key=op://Private/env-devict-infra/SSH_PUBLIC_KEY
PDS_HOSTNAME=op://Private/env-devict-infra/PDS_HOSTNAME
PDS_ADMIN_EMAIL=op://Private/env-devict-infra/PDS_ADMIN_EMAIL
PDS_EMAIL_SMTP_URL=op://Private/env-devict-infra/PDS_EMAIL_SMTP_URL
PDS_EMAIL_FROM_ADDRESS=op://Private/env-devict-infra/PDS_EMAIL_FROM_ADDRESS
PDS_HCAPTCHA_SITE_KEY=op://Private/env-devict-infra/PDS_HCAPTCHA_SITE_KEY
PDS_HCAPTCHA_SECRET_KEY=op://Private/env-devict-infra/PDS_HCAPTCHA_SECRET_KEY
TF_VAR_spaces_access_id=op://Private/env-devict-infra/SPACES_ACCESS_KEY_ID
TF_VAR_spaces_secret_key=op://Private/env-devict-infra/SPACES_SECRET_ACCESS_KEY
PDS_BLOBSTORE_S3_ACCESS_KEY_ID=op://Private/env-devict-infra/SPACES_ACCESS_KEY_ID
PDS_BLOBSTORE_S3_SECRET_ACCESS_KEY=op://Private/env-devict-infra/SPACES_SECRET_ACCESS_KEY

DIGITALOCEAN_TOKEN and SSH_PUBLIC_KEY are reused from devserver; create the rest in 1Password. SMTP and hCaptcha keys are optional (only for 2FA email and captcha-gated account creation).

Object storage (blobs): create a Spaces key in the DigitalOcean control panel (API → Spaces Keys) with read/write access and store it as SPACES_ACCESS_KEY_ID / SPACES_SECRET_ACCESS_KEY in 1Password. Terraform provisions a private Spaces bucket (devict-pds-blobs by default; see terraform/spaces.tf) and the same key authenticates the PDS to read/write blobs. The PDS uses S3 storage automatically when the access key is present, and falls back to local disk otherwise. To switch an already-running PDS to S3, add the PDS_BLOBSTORE_S3_* block to /pds/pds.env and restart (just ssh then sudo systemctl restart pds).

Usage #

just plan     # terraform plan
just apply    # terraform apply
just setup    # ansible-playbook site.yaml
just deploy   # re-render templates + restart (no full setup)
just ssh      # ssh devict@<ip>

After install #

This PDS version (0.4.5009, shipping @atproto/pds 0.5.9) includes the built-in account management UI under https://<pds_hostname>/ — create/invite/migrate accounts from the browser without the goat CLI.

CLI alternative (ssh in):

sudo docker exec pds goat pds admin account create

See PLAN.md for the full design.