CLI tool
bluesky-social/pds avatar
bluesky-social/pds

Bluesky PDS: self-hosting your own Personal Data Server with Docker

Bluesky PDS (Personal Data Server) container image, compose file, and documentation

2,620 stars309 forksShellNOASSERTION

At a glance

What is it?
The official Bluesky PDS repository ships an installer, a compose file and pdsadmin tooling for running an AT Protocol Personal Data Server on a VPS. It is a single-tenant host, not a hosting platform, and the README is explicit about which setups it does not fit.
Who is it for?
Adopt bluesky-social/pds if you want one domain, one server and a small number of accounts you administer yourself, and you are comfortable with the installer taking over ports 80 and 443 on a fresh VPS. Do not adopt it if you need to run a PDS next to an existing web server on the same host, since the README states the install script assumes a fresh VPS and that you should not use it in that case.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What a Bluesky PDS actually is, and who ends up running one

A PDS is a Personal Data Server: the machine that holds your account's repository and serves it into the wider AT Protocol network. The repository README frames self-hosting as running your own server that is "capable of federating with the wider Bluesky social network", and it lists federated domain handles, feed generators, relays, app views, data hosting and moderation as the parts of the network that are open. The practical audience is narrow. The server recommendation table names 1 GB of RAM, one CPU core, 20 GB of SSD and a user count of 1 to 20. That is a personal server or a small group server, not a public signup service. If you are evaluating this because you want to offer accounts to strangers, the sizing table alone should tell you that this distribution is not aimed at you. The repository is mostly Shell, and what it ships is the operational layer: a Dockerfile, a compose.yaml, an installer.sh, a pdsadmin.sh and a pdsadmin/ directory, plus a monitoring/ directory and ACCOUNT_MIGRATION.md. The application code lives elsewhere, in bluesky-social/atproto for the TypeScript implementation and bluesky-social/indigo for the Go one, both linked from the README.

Three containers, host networking, and a bind mount at /pds

The compose.yaml defines the whole runtime in three services. Caddy runs as caddy:2 with network_mode: host, binds /pds/caddy/data and /pds/caddy/etc/caddy, and depends on the pds service. The PDS itself runs ghcr.io/bluesky-social/pds:0.4, also with host networking, bind-mounting /pds into the container and reading its configuration from /pds/pds.env via env_file. Watchtower runs ghcr.io/nicholas-fedor/watchtower:latest with the Docker socket mounted, WATCHTOWER_CLEANUP set to true and WATCHTOWER_SCHEDULE set to "@midnight", which is how the image gets updated on a running server. Host networking is the design decision worth noticing: nothing is published through Docker's port mapping, so the containers own the host's ports directly. That is also why the README says there is no need to set up TLS or a port 80 to 443 redirect yourself, since Caddy handles both. The Dockerfile shows what is inside the image. It builds the goat CLI from bluesky-social/goat at tag v0.2.2 in a build stage, copies it to /usr/local/bin/goat, then runs node --enable-source-maps index.ts with dumb-init as the entrypoint. PDS_PORT is set to 3000 and NODE_ENV to production. One environment variable in the Dockerfile is a deliberate workaround rather than a preference: UV_USE_IO_URING=0, annotated in the file as a response to potential performance issues with io_uring on that Node version.

Installing on Ubuntu or Debian and creating the first account

The README gives an interactive installer for Ubuntu 20.04, 22.04 and 24.04 and Debian 11, 12 and 13. Download it first, then run it with sudo. The script prompts for your public DNS address, an admin email address (which does not have to be on the same domain), and then walks you through creating a PDS user account with its own email address and handle.

bash
curl https://raw.githubusercontent.com/bluesky-social/pds/main/installer.sh > installer.sh
bash
sudo bash installer.sh

The README notes that if you plan to reuse an existing AT handle you can skip user account creation, and suggests that first-time operators create a test account under their own domain. On success the installer prints a banner block. Before any of this, two things must be true on the host: a public IPv4 address, and inbound access allowed on 80/tcp and 443/tcp. The README calls the firewall step one of the most common sources of misconfiguration and asks you to double check it, which is worth taking literally. DNS comes next, and it needs two records: an A record for the bare domain and a wildcard A record for the same IP, both with a TTL of your choosing (600 is suggested). The README states the wildcard record is required when allowing users to create new accounts. Verify with a resolver checker against the root domain and a couple of random subdomains; all of them should return the server's public IP.

Where the install script is the wrong tool

The README is unusually direct about this: the script assumes a relatively fresh VPS that is not concurrently hosting a web server or anything else on ports 80 and 443, and it says plainly that if you intend to run a PDS alongside an existing web server on the same VPS, you will not want to use this install script. That is a real boundary, not a caveat. Because both Caddy and the PDS use host networking, there is no port remapping to negotiate with an existing listener. An operator with nginx already answering on 443 has to assemble the deployment from the Dockerfile and compose.yaml by hand, and the README does not walk through that path. Two other gaps are visible in the repository layout rather than the prose. The compose file pins the PDS image at 0.4 while Watchtower updates containers on a nightly schedule, so the running version can move without a deliberate operator action; the README documents an update procedure, but anyone who wants reproducible deployments needs to reconcile that with the watchtower service. And the README does not document rollback. There is a section on migrating your PDS and one on fixing a relay desync, but nothing that describes reverting to a previous image tag after a bad update.

goat, pdsadmin and the operational surface

Two CLIs are in play. goat is built into the container image from bluesky-social/goat at v0.2.2 and is listed in the README under its own heading, which is the tool you reach for from the server side. pdsadmin is the repository's own shell tooling, shipped as pdsadmin.sh plus a pdsadmin/ directory, and the README covers it in sections on creating an account, creating an account with an invite code, SMTP, logging, monitoring and metrics, updating, environment variables, appearance customization, migration and relay desync repair. Configuration lives in /pds/pds.env, referenced by the compose file and covered by the README's environment variables section. For metrics, the Dockerfile carries a label stating that the bundled @atproto/pds exposes a "./telemetry" entry point and that operators who want metrics can load the OpenTelemetry SDK by setting NODE_OPTIONS=--import=@atproto/pds/telemetry in pds.env. The monitoring/ directory in the repository is the corresponding material. Read that label as the intended integration path: metrics are opt-in through an environment variable, not enabled by default.

How this differs from running the PDS from the atproto repository

The obvious alternative is the TypeScript PDS package in bluesky-social/atproto, which the README links as the location of the code. The difference is scope, not feature set. The atproto repository gives you the application and its build and test tooling; you supply the container, the reverse proxy, the TLS termination, the update mechanism and the admin scripts. This repository supplies those pieces as one opinionated stack: Caddy for certificates, a pinned image for the server, Watchtower for updates, an interactive installer for the initial configuration, and pdsadmin for day-two operations. Choosing this repository means accepting its opinions, including host networking and the assumption that the host is dedicated to the PDS. Choosing atproto means writing your own compose file and your own admin procedures, which is more work but leaves the ports, the proxy and the update cadence under your control. Neither is a superset of the other. The container image here is built from the service/ directory in this repository, which is where the packaged application lives.

Licence, maintenance and what an upgrade costs you

The repository carries LICENSE-APACHE.txt, LICENSE-MIT.txt and LICENSE.txt, and the Dockerfile labels the image org.opencontainers.image.licenses=MIT. The GitHub licence field reports NOASSERTION, so if the exact terms matter to your organisation, read the three licence files rather than the metadata. The last push to the default branch was on 2026-08-31, and the repository is not archived. There are no retrieved releases, so version tracking happens through the image tag and the installer script rather than through release notes. The upgrade cost is mostly operational. Watchtower runs at midnight with cleanup enabled, which means old images are removed and the previous version is not sitting on disk waiting to be restored. Combined with the absence of documented rollback, that is the thing to plan for: decide in advance which image tag you would pin in compose.yaml if an update went wrong, and know that doing so means also deciding what to do about the watchtower service. The README does document updating your PDS, so the forward path is covered; the reverse path is not.

Editorial conclusion

Adopt bluesky-social/pds if you want one domain, one server and a small number of accounts you administer yourself, and you are comfortable with the installer taking over ports 80 and 443 on a fresh VPS. Do not adopt it if you need to run a PDS next to an existing web server on the same host, since the README states the install script assumes a fresh VPS and that you should not use it in that case. Before running anything, confirm your DNS: an A record for the root domain and a wildcard A record for *.example.com pointing at the server, both verifiable through a resolver checker. The wildcard is not optional if you intend to let users create accounts, because the README ties it to account creation on your PDS.

Frequently asked questions

How do I make my own PDS?

The README instructs you to download installer.sh with curl from the repository's main branch and run it with sudo on a fresh Ubuntu or Debian VPS. The script is interactive and prompts for your public DNS address, an admin email address, and a PDS user account with its own email address and handle.

What is Bluesky Social?

The README describes Bluesky as a social media application built on AT Protocol, and points to bsky.social for more information. The PDS repository is the server side of that picture: it hosts a Personal Data Server that federates with the wider network.

How much does Bluesky Social cost?

The repository does not state a price for anything, and no pricing appears in the README or the compose file. What it does specify is the server you need: a VPS with a public IPv4 address, a public DNS name, and inbound access on 80/tcp and 443/tcp, with 1 GB RAM, one CPU core and 20 GB SSD recommended.

Who is funding Bluesky Social?

The repository material does not cover funding or organisational backing for Bluesky Social. Nothing in the README, the Dockerfile or the compose file addresses it.

Official sources

  1. bluesky-social/pds on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bluesky-social-pds.svg)](https://hysenlabs.com/projects/bluesky-social-pds)