---
title: "Deploy Bluesky PDS | (Just Updated) Self-Host AT Protocol Server, Nothing to Configure"
description: "Bluesky PDS self-hosted AT Protocol server, accounts and blobs on a volume"
category: "Other"
url: https://railway.com/deploy/bluesky-pds-or-just-updated-self-host-at
---

# Deploy Bluesky PDS | (Just Updated) Self-Host AT Protocol Server, Nothing to Configure

Bluesky PDS self-hosted AT Protocol server, accounts and blobs on a volume

**[Deploy Bluesky PDS | (Just Updated) Self-Host AT Protocol Server, Nothing to Configure on Railway](https://railway.com/template/bluesky-pds-or-just-updated-self-host-at)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/bluesky-pds-or-just-updated-self-host-at/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Other

## Template content

### pds

- **Image:** ghcr.io/bon5co/pds-railway:latest@sha256:d9e4926d45a59e5a44fe568c5e40947880c3b26b4db477439abdb355687373ff
- **Health check:** /xrpc/_health
- **Public domain:** Yes

## Documentation

# Deploy and Host Bluesky PDS on Railway

**Built for an LLM agent to deploy and use.** The deploy form has no fields. A Bluesky Personal Data Server (the AT Protocol server that stores your accounts, posts and media) comes up with a generated admin password, a volume that keeps every account across redeploys, and a public HTTPS address. The contract below lists every address, variable name and one script that creates an account and publishes a post.

Bluesky PDS here is the official `ghcr.io/bluesky-social/pds` image, version 0.4.5051, the open-source server that lets you host your own Bluesky or AT Protocol account instead of living on someone else's. It follows the same public protocol as bsky.social, so the app, feeds and other servers can read your account.

## About Hosting Bluesky PDS

This template runs one service from a wrapper image pinned by digest. The wrapper makes the PDS listen on Railway's port, takes its hostname from the service's public domain, and keeps a stable PLC rotation key (the key that can recover your account identity), so nothing needs to be typed in. Accounts, repositories and uploaded blobs live on the volume mounted at `/pds`. The admin password `PDS_ADMIN_PASSWORD` and the token-signing secret `PDS_JWT_SECRET` are generated at deploy time. Account creation is invite-only; an agent mints invite codes through the admin API.

New handles default to subdomains of the server hostname (`NAME.YOUR-RAILWAY-DOMAIN`). A Railway domain has no wildcard DNS, so those subdomains do not resolve on their own; the account works on this server, but for handles that other servers and the Bluesky app can resolve, attach a custom domain and use handles on a domain you own (a `_atproto` TXT record or `/.well-known/atproto-did` on that domain).

Creating an account registers its DID with the public PLC directory, and the image announces the server to `bsky.network` so the relay can crawl it. That is the normal behaviour of every PDS.

### Agent contract

- Public URL: `PDS_PUBLIC_URL` (`https://` plus the service's `RAILWAY_PUBLIC_DOMAIN`)
- Private URL: `http://pds.railway.internal:8080` (also `PDS_PRIVATE_URL`)
- Credentials: `PDS_ADMIN_PASSWORD` (generated, 32 characters); account passwords are chosen by whoever creates the account
- Auth: admin calls use HTTP basic auth with user `admin` and password `$PDS_ADMIN_PASSWORD`; account calls use `Authorization: Bearer` followed by the `accessJwt` returned by `createSession`
- Health: `GET /xrpc/_health` returns 200 with `{"version":"0.4.5051"}`

```bash
# needs: PDS_PUBLIC_URL and PDS_ADMIN_PASSWORD from the pds service variables
HOST="${PDS_PUBLIC_URL#https://}"
curl -s "$PDS_PUBLIC_URL/xrpc/_health"; echo
curl -s -o /dev/null -w "no admin auth: %{http_code}\n" -X POST -H 'content-type: application/json' -d '{"useCount":1}' "$PDS_PUBLIC_URL/xrpc/com.atproto.server.createInviteCode"
CODE=$(curl -s -u "admin:$PDS_ADMIN_PASSWORD" -X POST -H 'content-type: application/json' -d '{"useCount":1}' "$PDS_PUBLIC_URL/xrpc/com.atproto.server.createInviteCode" | sed 's/.*"code":"\([^"]*\)".*/\1/')
curl -s -X POST -H 'content-type: application/json' -d "{\"email\":\"agent@example.com\",\"handle\":\"agent.$HOST\",\"password\":\"agent-password-1\",\"inviteCode\":\"$CODE\"}" "$PDS_PUBLIC_URL/xrpc/com.atproto.server.createAccount" | sed 's/"\(accessJwt\|refreshJwt\)":"[^"]*"/"\1":"..."/g'; echo
JWT=$(curl -s -X POST -H 'content-type: application/json' -d "{\"identifier\":\"agent.$HOST\",\"password\":\"agent-password-1\"}" "$PDS_PUBLIC_URL/xrpc/com.atproto.server.createSession" | sed 's/.*"accessJwt":"\([^"]*\)".*/\1/')
curl -s -X POST -H "authorization: Bearer $JWT" -H 'content-type: application/json' -d "{\"repo\":\"agent.$HOST\",\"collection\":\"app.bsky.feed.post\",\"record\":{\"\$type\":\"app.bsky.feed.post\",\"text\":\"hello from my own PDS\",\"createdAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}" "$PDS_PUBLIC_URL/xrpc/com.atproto.repo.createRecord"; echo
curl -s -G --data-urlencode "repo=agent.$HOST" --data-urlencode "collection=app.bsky.feed.post" "$PDS_PUBLIC_URL/xrpc/com.atproto.repo.listRecords"; echo
```

A fresh deploy prints `{"version":"0.4.5051"}`, then `no admin auth: 401`, then the new account (`handle`, `did:plc:...`, and its DID document), then the new record's `at://` URI with `"validationStatus":"valid"`, and finally the record listed back with the text `hello from my own PDS`. The script creates a real account and registers its DID in the public PLC directory; delete the account afterwards with `com.atproto.server.deleteAccount` if you do not want to keep it.

## Why Deploy Bluesky PDS on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Bluesky PDS on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

- **Agent-ready** - no deployer fields, a contract block and one working create-account-and-post script.
- **Nothing to configure** - the admin password, JWT secret and rotation key are generated, the hostname comes from the Railway domain.
- **Persistent** - a volume at `/pds` holds accounts and blobs, the rotation key stays stable across redeploys, and the image is pinned by digest.
- **Health-checked** - Railway restarts the server if `/xrpc/_health` stops answering.

## Common Use Cases

- **Own your Bluesky identity** - host your account on a server you control and move it between servers without losing followers.
- **Agent and bot accounts** - give an AI agent its own AT Protocol repository and post to the network through the standard API.
- **Community or organisation PDS** - host accounts for a team or a project under your own domain.
- **AT Protocol development** - run a real PDS to test feeds, labelers and clients against.

## Dependencies for Bluesky PDS Hosting

- None. Email is optional; set `PDS_EMAIL_SMTP_URL` and `PDS_EMAIL_FROM_ADDRESS` if you want verification and reset mails.

### Deployment Dependencies

- [Bluesky PDS repository](https://github.com/bluesky-social/pds)
- [AT Protocol documentation](https://atproto.com/guides/self-hosting)
- [Wrapper image source](https://github.com/bon5co/pds-railway)

### Implementation Details

| Variable | Purpose |
| --- | --- |
| `PDS_ADMIN_PASSWORD` | Generated password for the admin API (basic auth user `admin`). |
| `PDS_JWT_SECRET` | Generated secret that signs session tokens. |
| `PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX` | Generated 64-character hex key that can recover account identities. Back it up. |
| `PDS_HOSTNAME` | The service's public domain; used in handles and DIDs. |
| `PDS_PUBLIC_URL` | HTTPS address of the server. |
| `PDS_PRIVATE_URL` | Address other services in the project use. |

The PDS listens on Railway's `PORT` (8080). Changing `PDS_HOSTNAME` after accounts exist changes the issuer their sessions were signed for; attach a custom domain before creating accounts you want to keep.


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/bluesky-pds-or-just-updated-self-host-at
