Personal Site
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Blog post about hosting a pds on a raspberry pi

+260
src/content/blog/assets/blue-skies.png

This is a binary file and will not be displayed.

src/content/blog/assets/pds-pi-graph.png

This is a binary file and will not be displayed.

+260
src/content/blog/pds-on-a-pi.md
··· 1 + --- 2 + title: PDS on a pi 3 + bio: installing a pds on my raspberry pi; Photo by Ritam Baishya on Unsplash 4 + banner: blue-skies.png 5 + pub: 2025-09-13 6 + --- 7 + 8 + I want to self host my own pds for atproto, but I wanna host it on my own hardware in my room, since that feels fun and whimsical. I've got a pi 5 sat on my floor which I'm going to use for this, and then I'll route it out of my vps using tailscale and caddy. Currently the pi doesn't have ethernet or constant power (i attached a screen to it and forgot how to take it off so i turn it off at night lmfao) so I'm going to create a test account (at://test.vielle.dev) and use that for now. This'll also be a chance to create a did:web account to mess with. 9 + 10 + > _Note_: I'm writing this blog post while I do it so its going to be messy 11 + 12 + The plan is to have all my services running on docker compose, which then can be accessed by using tailscale on the pi host. This then goes into tailscale on the vps, which goes into caddy, which goes out to the net. Little complex but should work. Heres a graph 13 + 14 + <style> 15 + /* select all images who's alt text starts with "Traffic from my docker containers" */ 16 + img[alt^="Traffic from my docker containers"] { 17 + image-rendering: auto; 18 + } 19 + </style> 20 + 21 + ![Traffic from my docker containers in my pi go to tailscale on the pi. This goes to tailscale on the vps. This + other docker containers go into a caddy docker container, which goes into the outside internet](assets/pds-pi-graph.png) 22 + 23 + ## 1. Get a dummy service on the pi 24 + 25 + I'm gonna get a dummy ping/pong https thing on the pi which i can test forwarding. Presumably if this works, then the PDS will work too when I add it. 26 + 27 + `ping-pong/main.ts` 28 + 29 + ```ts 30 + // using Deno 31 + // new http server on 0.0.0.0:8000 which just sends "Hello" 32 + Deno.serve({ port: 8000, hostname: "0.0.0.0" }, (req) => { 33 + return new Response("Hello! " + req.url); 34 + }); 35 + ``` 36 + 37 + `ping-pong/Dockerfile` 38 + 39 + ```Dockerfile 40 + FROM denoland/deno:latest 41 + WORKDIR /app 42 + COPY . . 43 + CMD ["deno", "run", "--allow-net", "main.ts"] 44 + ``` 45 + 46 + `compose.yml` 47 + 48 + ```yaml 49 + services: 50 + pingpong: 51 + build: ./ping-pong 52 + restart: unless-stopped 53 + ports: 54 + - 8000:8000 55 + ``` 56 + 57 + It works, so now I'm going to add a caddy rule to route port 8000 of my pi (`http://pi:8000`) to `https://pds.vielle.dev:443`. 58 + 59 + ## 2. Routing traffic from vps -> pi 60 + 61 + All I need to do is add this code to my Caddyfile and run it locally to test. 62 + 63 + ```caddyfile 64 + pds.{$HOST:localhost} { 65 + reverse_proxy pi:8000 66 + } 67 + ``` 68 + 69 + Once I'm ready to deploy, this SHOULD just be a matter of pushing the commit to master and assuming my shitty cicd works it'll be deployed, along with this post! 70 + 71 + ## 3. Installing the pds in the container. 72 + 73 + For this one, we're gonna look at the pds setup from <https://github.com/bluesky-social/pds/>. This has 3 parts: 74 + 75 + - A caddy container 76 + - The pds 77 + - Watchtower 78 + 79 + ### 3.1. Caddyfile 80 + 81 + Starting with the caddy container, let's just make sure our vps has anything in the caddy config its missing. 82 + The config is below, [found here](https://github.com/bluesky-social/pds/blob/00078b4658def4eb419efab717fa970d01fce045/installer.sh#L302) 83 + 84 + ```Caddyfile 85 + { 86 + email ${PDS_ADMIN_EMAIL} 87 + on_demand_tls { 88 + ask http://localhost:3000/tls-check 89 + } 90 + } 91 + 92 + *.${PDS_HOSTNAME}, ${PDS_HOSTNAME} { 93 + tls { 94 + on_demand 95 + } 96 + reverse_proxy http://localhost:3000 97 + } 98 + ``` 99 + 100 + This is using global config for an email address, which is set further up the script, but I'll configure with environment variables, and for on demand tls which is on the pds. This means the pds can decide which subdomains get tls, with any level of wildcarding. I don't plan on using on demand tls for anything else on my site right now, but since it can't be scoped I might need to make a custom handler myself. 101 + 102 + Anyway heres the updated caddyfile config: 103 + 104 + ```caddyfile 105 + { 106 + email {$PDS_ADMIN_EMAIL:404@vielle.dev} 107 + on_demand_tls { 108 + ask pi:8000/tls-check 109 + } 110 + } 111 + 112 + # ... 113 + 114 + *.pds.{$HOST:localhost}, pds.{$HOST:localhost} { 115 + tls { 116 + on_demand 117 + } 118 + reverse_proxy pi:8000 119 + } 120 + ``` 121 + 122 + This refuses to connect but I'm going to assume its because the on demand tls is failing so theres just no cert which I can tell firefox to accept. 123 + 124 + ### 3.2. Watchtower 125 + 126 + Googling it, watchtower seems to be a way to automatically update containers? 127 + I'm just going to add it straight into the compose file lmao 128 + 129 + ```yaml 130 + services: 131 + pingpong: 132 + build: ./ping-pong 133 + restart: unless-stopped 134 + ports: 135 + - 8000:8000 136 + 137 + watchtower: 138 + container_name: watchtower 139 + image: containrrr/watchtower:latest 140 + network_mode: host 141 + volumes: 142 + - type: bind 143 + source: /var/run/docker.sock 144 + target: /var/run/docker.sock 145 + restart: unless-stopped 146 + environment: 147 + WATCHTOWER_CLEANUP: true 148 + WATCHTOWER_SCHEDULE: "@midnight" 149 + ``` 150 + 151 + ### 3.3. The PDS 152 + 153 + The reference compose file configures the pds like this: 154 + 155 + ```yaml 156 + pds: 157 + container_name: pds 158 + image: ghcr.io/bluesky-social/pds:0.4 159 + network_mode: host 160 + restart: unless-stopped 161 + volumes: 162 + - type: bind 163 + source: /pds 164 + target: /pds 165 + env_file: 166 + - /pds/pds.env 167 + ``` 168 + 169 + This is self explanatory; we make a pds directory, bind it to /pds, and make sure theres at least a pds.env file in there, which is the only file we need, based on installer.sh 170 + 171 + In installer.sh, they generate the env file like so: 172 + 173 + ```bash 174 + cat <<PDS_CONFIG >"${PDS_DATADIR}/pds.env" 175 + PDS_HOSTNAME=${PDS_HOSTNAME} 176 + PDS_JWT_SECRET=$(eval "${GENERATE_SECURE_SECRET_CMD}") 177 + PDS_ADMIN_PASSWORD=${PDS_ADMIN_PASSWORD} 178 + PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(eval "${GENERATE_K256_PRIVATE_KEY_CMD}") 179 + PDS_DATA_DIRECTORY=${PDS_DATADIR} 180 + PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATADIR}/blocks 181 + PDS_BLOB_UPLOAD_LIMIT=52428800 182 + PDS_DID_PLC_URL=${PDS_DID_PLC_URL} 183 + PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL} 184 + PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID} 185 + PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL} 186 + PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID} 187 + PDS_CRAWLERS=${PDS_CRAWLERS} 188 + LOG_ENABLED=true 189 + PDS_CONFIG 190 + ``` 191 + 192 + Lets write our own pds.env file first: 193 + 194 + - PDS_HOSTNAME is just `pds.vielle.dev` 195 + - PDS_JWT_SECRET is just `openssl rand --hex 16` 196 + - PDS_ADMIN_PASSWORD is also just `openssl rand --hex 16`, but must be different 197 + - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX is `openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` 198 + - PDS_DATA_DIRECTORY is PDS_DATADIR, which is hardcoded to `/pds` in the shell. Since this is currrently hardcoded to `/pds`, we can hardcode it 199 + - PDS_BLOBSTORE_DISK_LOCATION is PDS_DATADIR/blocks, so `/pds/blocks` 200 + - PDS_BLOB_UPLOAD_LIMIT is set to `52428800` 201 + - PDS_DID_PLC_URL is `https://plc.directory` 202 + - PDS_BSKY_APP_VIEW_URL is `https://api.bsky.app` 203 + - PDS_BSKY_APP_VIEW_DID is `did:web:api.bsky.app` 204 + - PDS_REPORT_SERVICE_URL is `https://mod.bsky.app` 205 + - PDS_REPORT_SERVICE_DID is `did:plc:ar7c4by46qjdydhdevvrndac` 206 + - PDS_CRAWLERS is `https://bsky.network`, but I'll expand this to include things like <https://atproto.africa/> & bad example's EU relays 207 + - LOG_ENABLED is `true` 208 + 209 + So to generate all this, I'm just going to run 210 + 211 + ```sh 212 + mkdir ./pds 213 + cat <<EOF > ./pds/pds.env 214 + PDS_HOSTNAME=pds.vielle.dev 215 + PDS_JWT_SECRET=$(eval "openssl rand --hex 16") 216 + PDS_ADMIN_PASSWORD=$(eval "openssl rand --hex 16") 217 + PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(eval "openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32") 218 + PDS_DATA_DIRECTORY=./pds # edit: change this to /pds (see below) 219 + PDS_BLOBSTORE_DISK_LOCATION=./pds/blocks # edit: change this to /pds/blocks (see below) 220 + PDS_BLOB_UPLOAD_LIMIT=52428800 221 + PDS_DID_PLC_URL=https://plc.directory 222 + PDS_BSKY_APP_VIEW_URL=https://api.bsky.app 223 + PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app 224 + PDS_REPORT_SERVICE_URL=https://mod.bsky.app 225 + PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac 226 + PDS_CRAWLERS=https://bsky.network,https://atproto.africa,https://relay1.us-east.bsky.network,https://relay.fire.hose.cam,https://relay3.fr.hose.cam,https://relay.hayescmd.net,https://relay.xero.systems 227 + LOG_ENABLED=true 228 + EOF 229 + ``` 230 + 231 + Pretty much everythings set up for a pds now, last step is to configure pds in the compose file and it should be online ! 232 + 233 + Just got to add this to my compose file and it should all work! 234 + 235 + ```yaml 236 + pds: 237 + container_name: pds 238 + image: ghcr.io/bluesky-social/pds:0.4 239 + restart: unless-stopped 240 + # removed network_mode: host since it should still work without it 241 + # and instead bound port 3000 of container to 8000 of host 242 + ports: 243 + - 8000:3000 244 + volumes: 245 + - type: bind 246 + # source is relative 247 + source: ./pds 248 + target: /pds 249 + # env is relative 250 + env_file: 251 + - ./pds/pds.env 252 + ``` 253 + 254 + Ok. So. Seems i didn't configure it correctly: `Error: Must configure plc rotation key`. I was missing the `xxd` command and somehow missed that? A simple `sudo apt-get install xxd` should fix things. Running the env command again gives an error that the directory for the database doesnt exist !! How Fun !! 255 + 256 + The issue was that i had `./pds` in the environment variables, (which were used inside the container), when the container used `/pds` as the datadir. Simple fix, and now it all works!! 257 + 258 + ## Next steps: 259 + 260 + Deploy this post + caddy changes, create a test account or two, find a permanent setup for the pi, and setup regular backups of the pds; then migrate to it myself!!