Installing an AT Protocol PDS on a Synology NAS
My (completely biased) opinion is that most people who have a NAS at home own a Synology NAS. And I'm one of them! Take a moment to admire my DS716+II ! 🤩 A Synology NAS is the quiet workhorse humming in a corner somewhere, storing backups, media, half-finished side projects... When I realized I could run my own AT Protocol Personal Data Server (PDS), I had to try to put my trusty NAS to another good use. Running a PDS on a Synology NAS is great because: Plenty of storage: Most of us have way more NAS storage than we’ll ever use. Perfect for blob storage. Always on: The NAS is literally built to stay awake all year. Cheap to run: You already paid for the hardware. Might as well get more value out of it. Own your identity: No cloud host in the middle. Your Bluesky identity lives on your box. The standard installer.sh script really doesn’t like Synology DSM (or maybe it's the other way around, I'm not very good with understanding relationships!) Anyways, here is how I got it working. Why the Standard installer.sh Doesn't Work The official PDS installer expects a “normal” Linux environment. Synology DSM is not that. Here are a few issues I ran into: Synology paths are different: Everything lives under /volume1/ or /volume2/. Docker behaves differently: DSM wraps Docker in its own UI and handles permissions its own way. Ports 80 and 443 are already used: DSM uses them for its web interface. No systemd: The installer expects it and gets confused. Permissions can be tricky: It is easy to hit “permission denied” errors until the paths and ownership are set up properly. This guide avoids those problems by doing a manual setup that plays nicely with DSM. Prerequisites Before jumping in, make sure you have: A Synology NAS running DSM 7 or later Administrator access to your NAS Administrator access to your router A way to get a hostname (either a domain you own, or use Synology's free DDNS service) Basic SSH skills (being able to copy and paste commands is enough) Note: If your ISP gives you a dynamic IP (most do), Synology's built-in DDNS feature is perfect for this. You do not need to buy a domain name. Part 1: DSM Configuration Step 1: Enable SSH Access Log into DSM. Open Control Panel → Terminal & SNMP. Enable SSH service. Note the port (default is 22). Click Apply. You will use this to run the commands in the rest of the guide. Step 2: Install Docker Open Package Center in DSM. Search for Docker. Install it. Open Docker once to confirm it is working. Step 3: Create Required Directories SSH into your NAS: ssh your-username@your-nas-ip
Then create the folder structure: sudo mkdir -p /volume1/docker/bluesky-pds sudo mkdir -p /volume1/docker/bluesky-pds/pds-data sudo mkdir -p /volume1/docker/bluesky-pds/caddy/data sudo mkdir -p /volume1/docker/bluesky-pds/caddy/etc/caddy
Give yourself permission to work in there (replace your-username with your real DSM username): sudo chown -R your-username:users /volume1/docker/bluesky-pds
Step 4: Generate Caddy Instance UUID Caddy needs a UUID. Generate it like this: cd /volume1/docker/bluesky-pds uuidgen > caddy/data/caddy/instance.uuid
If uuidgen is missing, use Python: python3 -c "import uuid; print(uuid.uuid4())" > caddy/data/caddy/instance.uuid
Part 2: Router Configuration DSM uses ports 80 and 443 for its own web interface, so the idea is: Configure the router to expose ports 80 and 443 on the internet. Configure the router to forward them to ports 8080 and 8443 on your NAS. Step 1: Find Your NAS IP In DSM, go to Control Panel → Network → Network Interface and note your NAS IP address, for example 192.168.1.100. Step 2: Port Forwarding on Your Router In your router web interface, create two port forwarding rules that point to your NAS IP. Rule 1: Service name: PDS HTTP External port: 80 Internal IP: your NAS IP (for example 192.168.1.100) Internal port: 8080 Protocol: TCP Rule 2: Service name: PDS HTTPS External port: 443 Internal IP: your NAS IP Internal port: 8443 Protocol: TCP Fritz!Box users: Go to Internet → Port Sharing → Port Forwarding. Step 3: DNS Setup Most home internet connections have a dynamic IP address that changes periodically. If that is your situation (and it probably is), Synology's built-in Dynamic DNS (DDNS) feature is perfect for this. Option A: Dynamic DNS (Recommended for Most Users) If your ISP gives you a dynamic IP, use Synology's DDNS service: In DSM, go to Control Panel → External Access → DDNS. Click Add. Select Synology as the service provider (or choose another provider if you prefer). Enter a hostname like yourname.synology.me (or whatever you want). Click Test Connection to verify it works. Click OK to save. If you use Synology's DDNS, you get a hostname like yourname.synology.me. This works perfectly for your PDS. You can also use other DDNS providers (DuckDNS, No-IP, etc.) if you prefer. Synology will automatically update the DNS record whenever your public IP changes. The hostname you create here (e.g., yourname.synology.me) will be your PDS hostname. Option B: Static IP with Manual DNS If you have a static IP address (or a domain you manage yourself): Find your public IP using a site like https://whatismyipaddress.com/. Create an A record in your DNS provider: Type: A Name: yourname (or your chosen subdomain) Value: your public IP address TTL: 300 (or any reasonable value) If you want multiple user handles later, add a wildcard: Type: A Name: * Value: your public IP address TTL: 300 Part 3: PDS Configuration Files Step 1: docker-compose.yml Create the file /volume1/docker/bluesky-pds/docker-compose.yml and put this in it: services: caddy: container_name: caddy image: caddy:2 depends_on: - pds restart: unless-stopped extra_hosts: # Allow Caddy to reach services exposed on the NAS host (e.g. Kuma on 3001) - "host.docker.internal:host-gateway" volumes: - type: bind source: /volume1/docker/bluesky-pds/caddy/data target: /data - type: bind source: /volume1/docker/bluesky-pds/caddy/etc/caddy target: /etc/caddy - type: bind source: /volume1/docker/bluesky-pds/site target: /srv/site ports: - "8080:80" - "8443:443" pds: container_name: pds image: ghcr.io/bluesky-social/pds:latest restart: unless-stopped volumes: - type: bind source: /volume1/docker/bluesky-pds target: /pds - type: bind source: /volume1/docker/bluesky-pds/pds-data target: /pds-data env_file: - ./pds.env pdsdash: container_name: pdsdash image: nginx:alpine restart: unless-stopped volumes: - type: bind source: /volume1/docker/bluesky-pds/pds-dash/dist target: /usr/share/nginx/html - type: bind source: /volume1/docker/bluesky-pds/pds-dash/nginx.conf target: /etc/nginx/conf.d/default.conf watchtower: container_name: watchtower image: ghcr.io/nicholas-fedor/watchtower:latest volumes: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock restart: unless-stopped environment: WATCHTOWER_CLEANUP: true WATCHTOWER_SCHEDULE: "@midnight"
Step 2: pds.env Create /volume1/docker/bluesky-pds/pds.env with your configuration: PDS_HOSTNAME=yourname.synology.me PDS_ADMIN_EMAIL=your-email@example.com PDS_ADMIN_PASSWORD=YourSecurePassword123! PDS_JWT_SECRET=your-64-character-hex-secret-here PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=your-64-character-hex-key-here PDS_EMAIL_SMTP_URL=smtp://user:password@smtp.example.com:587 PDS_EMAIL_FROM_ADDRESS=no-reply@yourdomain.com PDS_DATA_DIRECTORY=/pds-data PDS_BLOBSTORE_DISK_LOCATION=/pds-data/blobs PDS_BLOBSTORE_DISK_TMP=/pds-data/tmp PDS_SQLITE_LOCATION=/pds-data/pds.sqlite PDS_BLOB_UPLOAD_LIMIT=104857600 PDS_ENABLE_ACCOUNT_REGISTRATION=true PDS_DID_PLC_URL=https://plc.directory PDS_BSKY_APP_VIEW_URL=https://api.bsky.app PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app PDS_REPORT_SERVICE_URL=https://mod.bsky.app PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac PDS_CRAWLERS=https://bsky.network LOG_LEVEL=info LOG_ENABLED=true
Replace these values: PDS_HOSTNAME: your domain name (if using Synology DDNS, this will be something like yourname.synology.me) PDS_ADMIN_EMAIL: your email address PDS_ADMIN_PASSWORD: a strong password PDS_JWT_SECRET: a 64 character hex string generated with openssl rand -hex 32 PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: another 64 character hex string PDS_EMAIL_SMTP_URL and PDS_EMAIL_FROM_ADDRESS: your SMTP details if you want email support Step 3: Caddyfile Create /volume1/docker/bluesky-pds/caddy/etc/caddy/Caddyfile: { email your-email@example.com on_demand_tls { ask http://pds:3000/tls-check } }
yourname.synology.me, *.yourname.synology.me { tls { on_demand } encode gzip zstd reverse_proxy http://pds:3000 }
hello.yourname.synology.me { tls your-email@example.com encode gzip zstd reverse_proxy http://pdsdash:80 }
Replace yourname.synology.me with your domain (or your Synology DDNS hostname if you went that route) and your-email@example.com with your email address. Part 4: Deployment Step 1: Start the Services From your SSH session on the NAS: cd /volume1/docker/bluesky-pds sudo docker compose pull sudo docker compose up -d
Step 2: Check Services sudo docker compose ps
You should see all containers with status Up. Step 3: Watch Logs Check the PDS logs: sudo docker compose logs -f pds
Press Ctrl+C to exit. Check Caddy logs: sudo docker compose logs -f caddy
Step 4: Test the Health Endpoint
From another machine (replace with your actual hostname):
curl -vk --resolve yourname.synology.me:443:YOUR_PUBLIC_IP
https://yourname.synology.me/xrpc/_health
Or if you want to test without specifying the IP: curl https://yourname.synology.me/xrpc/_health
You should get a JSON response like: {"version":"0.4.x"}
You can also open https://yourname.synology.me/ (or your DDNS hostname) in a browser to see the PDS banner.
Part 5: Creating Invites and First User
The goat CLI tool is preinstalled inside the PDS container. All commands here run on the NAS.
Step 1: Create an Invite Code
cd /volume1/docker/bluesky-pds
sudo docker compose exec pds goat pds admin create-invites
--count 1
--uses 1
This will output something like: code: yourname-synology-me-xxxxxx-xxxx uses: 1
Keep that code somewhere safe.
To create more invite codes in one go:
sudo docker compose exec pds goat pds admin create-invites
--count 5
--uses 3
Step 2: Create Your First User
Use your invite code to create your account:
sudo docker compose exec pds goat account create
--pds-host https://yourname.synology.me
--handle alice.yourname.synology.me
--email your-email@example.com
--password 'YourSecurePassword123!'
--invite-code yourname-synology-me-xxxxxx-xxxx
Of course, you'll need to replace:
alice.yourname.synology.me with your handle (it must fit the *.yourdomain pattern).
your-email@example.com with your email.
YourSecurePassword123! with your password.
yourname-synology-me-xxxxxx-xxxx with your actual invite code.
On success, goat will print your DID, which looks like did:plc:something.
Step 3: List Accounts
sudo docker compose exec pds goat account list
--pds-host https://yourname.synology.me
--admin-password 'YourSecurePassword123!'
Use the admin password you set in pds.env. Step 4: List Invite Codes sudo docker compose exec pds goat pds admin list-invites
Troubleshooting Port Conflicts If Docker complains about ports, check what is using them: sudo netstat -tuln | grep -E '8080|8443'
DNS and Certificates If TLS certificates fail: If using DDNS, check that it is updating correctly in Control Panel → External Access → DDNS. Check DNS resolution: dig yourname.synology.me (or your hostname). Test port forwarding with tools like telnet or nc. Inspect Caddy logs: sudo docker compose logs caddy
Permission Issues If you see permission errors, reset ownership and permissions: sudo chown -R $(whoami):users /volume1/docker/bluesky-pds sudo chmod -R 755 /volume1/docker/bluesky-pds
Containers Do Not Start Check logs: sudo docker compose logs pds sudo docker compose logs caddy
Restart everything: sudo docker compose down sudo docker compose up -d
Maintenance View Logs
PDS logs
sudo docker compose logs -f pds
Caddy logs
sudo docker compose logs -f caddy
All services
sudo docker compose logs -f
Update PDS Watchtower will update images regularly, but you can also do it manually: cd /volume1/docker/bluesky-pds sudo docker compose pull sudo docker compose up -d
Backups
Back up your PDS data regularly:
sudo tar -czf /volume1/backups/pds-backup-$(date +%Y%m%d).tar.gz
/volume1/docker/bluesky-pds/pds-data
Security Tips Rotate secrets after the initial setup. Use strong passwords for admin and user accounts. Restrict SSH access if possible. Keep DSM and Docker up to date. Next Steps Migrate an existing Bluesky account using the official migration guide. Invite friends by generating more invite codes. Set up monitoring using the pdsdash dashboard. Automate your backup process. Conclusion Wow! that was quite a mouthful, and honestly, from the length of this, it might look difficult but it really is not. If you got this far, you now have a working Bluesky PDS running on your Synology NAS. No rented VPS, no mystery cloud, just your hardware, your data, and your identity. It feels good to run your own corner of the network. Additional Resources Bluesky PDS GitHub Repository ATProto Documentation Synology Docker Documentation
Discussion in the ATmosphere