My (completely biased) opinion is that most people who have a NAS at home own a Synology NAS. I'm one of them, I have a DS716+II
If you're anything like me, your Synology NAS is already the quiet workhorse humming in a corner somewhere, storing backups, media, half-finished side projects, and who knows what else. When I realized I could run my own AT Protocol Personal Data Server (PDS) on it, I knew I had to try.
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 likeyourname.synology.me)PDS_ADMIN_EMAIL: your email addressPDS_ADMIN_PASSWORD: a strong passwordPDS_JWT_SECRET: a 64 character hex string generated withopenssl rand -hex 32PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: another 64 character hex stringPDS_EMAIL_SMTP_URLandPDS_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.mewith your handle (it must fit the*.yourdomainpattern).your-email@example.comwith your email.YourSecurePassword123!with your password.yourname-synology-me-xxxxxx-xxxxwith 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
telnetornc. - 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
pdsdashdashboard. - 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.