I'm not usually a big user of social networks, but with my new blog, I wanted a way to share updates on social media.
I went with Bluesky for its decentralized nature, which aligns with my interest in the growing trend of decentralized social platforms.
In this write-up, I will document the steps I took to install [Bluesky’s PDS (Personal Data Server)](https://github.com/bluesky-social/pds) on my setup, an Ubuntu server running Docker.
Thanks to this [write-up](https://mattdyson.org/blog/2024/11/self-hosting-bluesky-pds), which was a huge help to do mine.
While most of it was very useful, some parts differed since my Docker setup is different.
Also, I encountered difficulties when trying to change my handle to the root domain, so I used a different method to achieve this.
As a result, I hope this guide will provide additional value and be helpful to others.
### Installing the PDS Using Docker
The starting point is the official bluesky [compose.yaml](https://github.com/bluesky-social/pds/blob/main/compose.yaml). However, my environment already include other services, including an [NGINX proxy with ACME Companion](https://github.com/nginx-proxy/acme-companion) to automatically generate and renew TLS certificates for my http services (see [Basic usage](https://github.com/nginx-proxy/acme-companion/wiki/Basic-usage)).
Therefore, I modify this ```compose.yaml``` to reuse my proxy setup to provide a reverse proxy for the PDS container, eliminating the need for the reverse proxy (Caddy) provided in the initial file. I also remove Watchtower, as I use my own scripts to handle image updates.
Finally, my ```compose.yaml``` is pretty simple file looks like this:
```yaml
version: '3.9'
services:
pds:
container_name: pds
image: ghcr.io/bluesky-social/pds:0.4
network_mode: host
restart: unless-stopped
volumes:
- type: bind
source: <SOMEPATHINHOST>/pds # Change this for
target: /pds
env_file:
- pds.env
```
Most of the setup is to adapt with environment variables in the environment file ```pds.env```:
```ini
# PDS_HOSTNAME: The domain name of your PDS service (e.g., domain.com)
PDS_HOSTNAME=<YOURDOMAIN>
# PDS_SERVICE_HANDLE_DOMAINS: A suffix for the domain to be used with the service (e.g., .domain.com)
PDS_SERVICE_HANDLE_DOMAINS=.<YOURDOMAIN>
# PDS_JWT_SECRET: A secret key for signing JWT tokens, needed for secure authentication
PDS_JWT_SECRET=<SECRETHERE>
# PDS_ADMIN_PASSWORD: The admin password for accessing the PDS service (use a strong password generated by a password manager)
PDS_ADMIN_PASSWORD=<ADMINPASSWORD>
# PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: A private key for cryptographic operations in hex format
# PDS_CRAWLERS: URL for the crawlers to scrape data from (e.g., bsky.network)
PDS_CRAWLERS=https://bsky.network
# VIRTUAL_HOST: The virtual host for your PDS service (e.g., pds.domain.com)
VIRTUAL_HOST=<PDSHOSTNAME>
# VIRTUAL_PORT: The port number for the virtual host (e.g., 3000)
VIRTUAL_PORT=3000
# LETSENCRYPT_HOST: The host for generating a Let's Encrypt SSL certificate (e.g., pds.domain.com)
LETSENCRYPT_HOST=<PDSHOSTNAME>
# LETSENCRYPT_EMAIL: The email used for Let's Encrypt certificate registration (e.g., admin@domain.com)
LETSENCRYPT_EMAIL=<LETSENCRYPTEMAIL>
```
Most variable comments are self-explanatory, but here are some additional hints for a few of them.
To generate the ```PDS_JWT_SECRET``` , you can use the following command:
```sh
$ openssl rand --hex 16
```
For ```PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX```, you can use
```sh
$ openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32
```
Since I run my own mail server on my domain, I use my SMTP server by specifying the information in the format ```smtp://<USERNAME>:<PASSWORD>@<SMTP HOSTNAME>```. If you don't have your own mail server, as mentioned in the official documentation, you can use services like [resend](https://resend.com/) as an alternative.
As I mention before, as I have already have the [NGINX proxy with ACME Companion set up](https://github.com/nginx-proxy/acme-companion), I only need to add the following lines to make the PDS container's port 3000 accessible on ```<PDS HOSTNAME>``` over HTTPS (e.g., https://pds.domain.com) through the reverse proxy:
```ini
VIRTUAL_HOST=<PDSHOSTNAME>
VIRTUAL_PORT=3000
LETSENCRYPT_HOST=<PDSHOSTNAME>
LETSENCRYPT_EMAIL=<LETSENCRYPTEMAIL>
```
The container is now ready to run. To start everything, use the following command to verify is everything looks find:
```sh
$ docker-compose up
```
#### Ensuring Everything the PDS Running as Expected
As directed in the documentation [https://atproto.com/guides/self-hosting](https://atproto.com/guides/self-hosting), to check that everything is online and working, you can visit `https://<PDS HOSTNAME>>/xrpc/_health` in your browser. You should see a JSON response with the version, like:
```json
{"version":"0.2.2-beta.2"}
```
Additionally, you can use an online WebSocket tester (e.g. [piehost.com](https://piehost.com/websocket-tester))
and entered the following URL: `wss://<PDS HOSTNAME>/xrpc/com.atproto.sync.subscribeRepos?cursor=0`
If everything is configured correctly, the test should indicate that the connection has been successfully established.
### Get your Root Domain (e.g domain.com) as your Bluesky Handle
You cannot directly create a handle for the root domain (e.g., domain.com). Therefore, we need to create an account with a different handle (e.g., handle.domain.com) and then verify the root domain handle ownership using [Decentralised Identifier (DID)](https://atproto.com/specs/did) . We can verify using DNS or HTTP server verification. It seems you can use either, but I did both to be sure and can't confirm if only one is enough.
#### Create account
Since the administrative tools are included in the Docker image, they do not need to be installed separately, as follows:
Make sure to record the handle, DID, and password for future use.
I should be able, at this step, to connect from the Bluesky client (e.g., [https://bsky.app/](https://bsky.app/)) using your custom domain and verify your email address.
Go to [https://bsky.app/](https://bsky.app/) > **Sign In** >In **Hosting Provider**, select **Custom** > enter your `<PDS HOSTNAME>` (e.g., pds.domain.com) and then use your credentials (email/password) to log in.
Then, you should be connected and able to verify your email address in the account settings by receiving a code, if your SMTP setup is correct.
Now, to validate our root domain, we first need to set up a Domain Ownership Verification method to confirm that we own the domain for the handle we want (e.g., domain.com).
#### Domain Ownership Verification using DNS
To do the verification of the ownership of your domain using DNS add a DNS TXT record ```_atproto.<DOMAIN>``` with ```did=<DID>```.
#### Domain Ownership Verification using HTTP
To do the verification of the ownership of your domain using HTTP you need make accecible a file on http server at ```<DOMAIN>/.well-known/atproto-did``` (e.g. https://domain.com/.well-known/atproto-did) that contain your ```<DID>```.
As I already have an Nginx server running to serve your homepage, I simply added a file at the path ```/.well-known/atproto-did``` in the root directory of my site, containing my ```<DID>``` value running the following :
For reference, here’s a sample Docker configuration for serving my homepage:
```yaml
version: '2'
services:
web:
image: nginx
container_name: nginx
restart: always
expose:
- 80
volumes:
-<WEBDATAPATHINHOST>:/usr/share/nginx/html:ro
environment:
- VIRTUAL_HOST=<DOMAIN>
- LETSENCRYPT_HOST=<DOMAIN>
- LETSENCRYPT_EMAIL=<LETSENCRYPTEMAIL>
```
#### Validation
To verify if the DNS or HTTP Domain Ownership Verification is set up correctly,
you can check on [https://bsky-debug.app/handle](https://bsky-debug.app/handle) by entering your handle.
#### Update Handle
We can now update your handle to your root domain !
However, I encountered an issue when trying to verify my handle directly in the Bluesky app ([https://bsky.app/](https://bsky.app/)). After logging into my account, I was unable to update my handle to the root domain due to errors. In **Settings > Account > Change Handle > I have my own domain**, selecting **Verify DNS Record** or **Verify Text File** would return an error.
However, I was able to use the [Go AT protocol CLI tool (goat)](https://github.com/bluesky-social/indigo/tree/main/cmd/goat) to successfully update my handle as follow.
Installing ```goat``` (require the Go toolchain):
```sh
$ go install github.com/bluesky-social/indigo/cmd/goat@latest
```
Then, update your handle using the following commands: