Our Personal Data Server from scratch!
0

Configure Feed

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

tranquil-pds / docs / 2_INSTALL_CONTAINERS.md
14 kB 426 lines
1# Tranquil PDS containerized production deployment 2 3This guide covers deploying Tranquil PDS using containers with podman. 4 5- **Debian 13+**: Uses systemd quadlets 6- **Alpine 3.23+**: Uses per-container OpenRC init scripts 7 8## Prerequisites 9 10- A server :p 11- Disk space for blobs, around* 1GB per active user as a baseline 12- A domain name pointing to your server's IP 13- A **wildcard TLS certificate** for `*.pds.example.com`, since user handles are served as subdomains 14- Root/sudo/doas access 15 16> 🦪 Lewis 17> 18> * "around" here meaning "at absolute least!" 19 20## Reverse proxy 21 22The bundled pod ships nginx as its reverse proxy, and this guide uses it throughout. nginx is just the default. Swap in whatever you prefer. Tranquil serves its API and web UI on a single port, so any reverse proxy works once it forwards to the app on `[::1]:3000`. 23 24Caddy is one good option. It grabs & renews TLS certificates automatically, including the wildcard this setup needs (if you're lucky, which Lewis is not), so the manual certbot steps later become unnecessary. 25 26### Terminating TLS in Tranquil 27 28You can also skip the reverse proxy and let Tranquil terminate TLS itself. Set `TLS_CERT_PATH` and `TLS_KEY_PATH` (or the `[server.tls]` block in the config file) to your cert chain and private key. 29 30Tranquil does not request or renew certs. Keep using certbot, acme.sh, lego, step, or whatever you're already using. After each renewal, send the process `SIGHUP` to reload the certificates and key. Live requests are unaffected and new connections pick up the new cert. 31 32### Client IP and forwarded headers 33 34Rate limiting and device records are based on the client IP ofc. Behind a reverse proxy, Tranquil reads it from `X-Forwarded-For`, counting hops from the right. You can set `TRUSTED_PROXY_COUNT` for how many proxies to trust. Leave it unset to let Tranquil assume the count. We assumes 1 proxy when something else terminates TLS, and 0 when Tranquil terminates TLS itself. At 0 it uses the direct conn address and ignores forwarded headers that a direct client could maliciously invent. 35 36 37## Quickstart (docker/podman compose) 38 39If you just want to get running quickly: 40 41```sh 42cp example.toml config.toml 43``` 44 45Edit `config.toml` with your values. Generate secrets with `openssl rand -base64 48`. 46 47`docker-compose.prod.yaml` pulls the prebuilt image from atcr.io. Sign in to the registry first with `podman login atcr.io`. 48 49nginx will not start without a certificate, so create a temporary self-signed one, bring the stack up, then swap in a real wildcard cert: 50```sh 51mkdir -p certs 52openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 53 -keyout certs/privkey.pem -out certs/fullchain.pem \ 54 -subj "/CN=pds.example.com" 55podman-compose -f docker-compose.prod.yaml up -d 56``` 57 58To build the image from source instead, run `podman build -t atcr.io/tranquil.farm/tranquil-pds:latest .` before bringing the stack up. 59 60User handles are subdomains, so the real certificate must be a wildcard for `*.pds.example.com`, which requires DNS-01 validation. Follow the DNS cert steps in the Wildcard TLS certificate section below, then: 61```sh 62podman-compose -f docker-compose.prod.yaml restart nginx 63``` 64 65## Standalone container without compose 66 67If you already have postgres running on the host, you can run just the app container. 68 69Pull the image. atcr.io requires authentication, so sign in first: 70```sh 71podman login atcr.io 72podman pull atcr.io/tranquil.farm/tranquil-pds:latest 73``` 74 75Run with host networking so it can reach postgres on localhost, and mount config + storage: 76```sh 77podman run -d --name tranquil-pds \ 78 --network=host \ 79 -v /etc/tranquil-pds/config.toml:/etc/tranquil-pds/config.toml:ro,Z \ 80 -v /var/lib/tranquil-pds:/var/lib/tranquil-pds:Z \ 81 atcr.io/tranquil.farm/tranquil-pds:latest 82``` 83 84To build from source instead, run `podman build -t atcr.io/tranquil.farm/tranquil-pds:latest .` and use that tag. 85 86Then point your reverse proxy at the app on port 3000 for every route. With nginx that looks like: 87 88```nginx 89location /xrpc/ { 90 proxy_pass http://[::1]:3000; 91 # full proxy headers are in deploy/nginx/nginx-pod.conf 92} 93 94location / { 95 proxy_pass http://[::1]:3000; 96 proxy_http_version 1.1; 97 proxy_set_header Host $host; 98 proxy_set_header X-Real-IP $remote_addr; 99 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 100 proxy_set_header X-Forwarded-Proto $scheme; 101} 102``` 103 104With Caddy the equivalent is a one-line `reverse_proxy [::1]:3000`, and it handles TLS for you. See `deploy/nginx/nginx-pod.conf` in the repo for the full nginx config with all routes. 105 106The end!!! 107 108Or wait, you want more? Perhaps a deployment that comes back on server restart? 109 110--- 111 112# Common setup 113 114Both service-managed deployments share the steps below. Do these first, then jump to the Debian or Alpine section for the init-specific stuff, and finish in the Wildcard TLS certificate section. 115 116## Install podman 117 118**Debian:** 119```bash 120apt update 121apt install -y podman 122``` 123 124**Alpine:** 125```sh 126apk update 127apk add podman fuse-overlayfs 128rc-update add cgroups 129rc-service cgroups start 130``` 131 132## Create the directory structure 133 134```sh 135mkdir -p /srv/tranquil-pds/{postgres,blobs,store,certs,acme,config} 136``` 137 138## Clone the repo and pull the image 139 140The repo provides the quadlet, OpenRC, and nginx files. The image comes prebuilt from atcr.io. Sign in before pulling: 141```sh 142cd /opt 143git clone https://tangled.org/tranquil.farm/tranquil-pds tranquil-pds 144podman login atcr.io 145podman pull atcr.io/tranquil.farm/tranquil-pds:latest 146``` 147 148To build from source instead, tag it with the same name so the service uses it: 149```sh 150cd tranquil-pds && podman build -t atcr.io/tranquil.farm/tranquil-pds:latest . 151``` 152 153## Create a configuration file 154 155```sh 156cp /opt/tranquil-pds/example.toml /srv/tranquil-pds/config/config.toml 157chmod 600 /srv/tranquil-pds/config/config.toml 158``` 159 160Edit `/srv/tranquil-pds/config/config.toml` and fill in your values. Generate secrets with: 161```sh 162openssl rand -base64 48 163``` 164 165> 🦪 Lewis 166> 167> Every config option can also be set via an environment variable 168> named in the comments in `example.toml`. Environment variables always take 169> precedence over the config file. 170 171## Create the nginx config and database secret 172 173The bundled pod runs nginx as its proxy. The app and nginx share the pod's network namespace, so the proxy reaches the app on `[::1]:3000`: 174```sh 175cp /opt/tranquil-pds/deploy/nginx/nginx-pod.conf /srv/tranquil-pds/config/nginx.conf 176``` 177 178Create the podman secret for the database password. Use the same password in the `database.url` of your `config.toml`: 179```sh 180echo "$DB_PASSWORD" | podman secret create tranquil-pds-db-password - 181``` 182 183## Create a temporary TLS certificate 184 185The nginx that comes out of the box will not start without a certificate. The real wildcard cert needs DNS-01 validation and is set up once the stack is running, so for now let's add a self-signed placeholder so the services can start: 186```sh 187openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 188 -keyout /srv/tranquil-pds/certs/privkey.pem \ 189 -out /srv/tranquil-pds/certs/fullchain.pem \ 190 -subj "/CN=pds.example.com" 191``` 192 193If your proxy obtains its own certificates, like Caddy does, you can skip this step! 194 195The app runs migrations itself on first boot btw, so there is no separate migration step if you're looking for one. 196 197--- 198 199# Debian with systemd quadlets 200 201Quadlets are a nice way to run podman containers under systemd. 202 203## Install quadlet definitions 204 205Copy the quadlet files from the repository: 206```bash 207mkdir -p /etc/containers/systemd 208cp /opt/tranquil-pds/deploy/quadlets/tranquil-pds.pod /etc/containers/systemd/ 209cp /opt/tranquil-pds/deploy/quadlets/tranquil-pds-db.container /etc/containers/systemd/ 210cp /opt/tranquil-pds/deploy/quadlets/tranquil-pds-app.container /etc/containers/systemd/ 211cp /opt/tranquil-pds/deploy/quadlets/tranquil-pds-nginx.container /etc/containers/systemd/ 212``` 213 214Optional quadlets for valkey and minio are also available in `deploy/quadlets/` if you need them. 215 216## Start services 217 218```bash 219systemctl daemon-reload 220systemctl start tranquil-pds-db 221sleep 10 222systemctl start tranquil-pds-app tranquil-pds-nginx 223``` 224 225## Enable all services 226 227```bash 228systemctl enable tranquil-pds-db tranquil-pds-app tranquil-pds-nginx 229``` 230 231## Configure firewall if you're into that sort of thing 232 233```bash 234apt install -y ufw 235ufw allow ssh 236ufw allow 80/tcp 237ufw allow 443/tcp 238ufw enable 239``` 240 241Now finish in the Wildcard TLS certificate section below. 242 243--- 244 245# Alpine with OpenRC 246 247Alpine uses OpenRC, not systemd, bless its soul. So instead of quadlets we use a set of OpenRC init scripts, one per container. 248 249## Install the OpenRC services 250 251Copy the init scripts. They run the pod, postgres, the app, and nginx as separate services ordered with `depend()`: 252```sh 253cp /opt/tranquil-pds/deploy/openrc/tranquil-pds-pod /etc/init.d/ 254cp /opt/tranquil-pds/deploy/openrc/tranquil-pds-db /etc/init.d/ 255cp /opt/tranquil-pds/deploy/openrc/tranquil-pds-app /etc/init.d/ 256cp /opt/tranquil-pds/deploy/openrc/tranquil-pds-nginx /etc/init.d/ 257chmod +x /etc/init.d/tranquil-pds-pod /etc/init.d/tranquil-pds-db /etc/init.d/tranquil-pds-app /etc/init.d/tranquil-pds-nginx 258``` 259 260The scripts default to `/srv/tranquil-pds` for data and `/srv/tranquil-pds/config/config.toml` for config. Override via `/etc/conf.d/tranquil-pds-app` and friends if your paths differ. 261 262## Start services 263 264Starting the nginx service pulls in the pod, postgres, and app through its dependencies: 265```sh 266rc-service tranquil-pds-nginx start 267``` 268 269## Enable services at boot time 270 271```sh 272rc-update add tranquil-pds-pod tranquil-pds-db tranquil-pds-app tranquil-pds-nginx 273``` 274 275## Configure firewall if you're into that sort of thing 276 277```sh 278apk add iptables ip6tables 279iptables -A INPUT -p tcp --dport 22 -j ACCEPT 280iptables -A INPUT -p tcp --dport 80 -j ACCEPT 281iptables -A INPUT -p tcp --dport 443 -j ACCEPT 282iptables -A INPUT -i lo -j ACCEPT 283iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 284iptables -P INPUT DROP 285ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT 286ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT 287ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT 288ip6tables -A INPUT -i lo -j ACCEPT 289ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 290ip6tables -P INPUT DROP 291rc-update add iptables 292rc-update add ip6tables 293/etc/init.d/iptables save 294/etc/init.d/ip6tables save 295``` 296 297Now finish in the Wildcard TLS certificate section below. 298 299--- 300 301# Wildcard TLS certificate 302 303This section sets up the real certificate for the bundled nginx. 304 305With the stack running behind the temporary self-signed certificate, swap in a real wildcard cert. User handles are served as subdomains like `nel.pds.example.com`, so the certificate must cover `*.pds.example.com`, which requires DNS-01 validation. 306 307Get a wildcard certificate using DNS validation: 308```sh 309podman run --rm -it \ 310 -v /srv/tranquil-pds/certs:/etc/letsencrypt:Z \ 311 docker.io/certbot/certbot:v5.2.2 certonly \ 312 --manual --preferred-challenges dns \ 313 -d pds.example.com -d '*.pds.example.com' \ 314 --agree-tos --email you@example.com 315``` 316 317Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. For automated renewal, use a DNS provider plugin or something. 318 319Link the certificates into place: 320```sh 321ln -sf /srv/tranquil-pds/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/certs/fullchain.pem 322ln -sf /srv/tranquil-pds/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/certs/privkey.pem 323``` 324 325Restart nginx to load them: 326 327**Debian:** 328```bash 329systemctl restart tranquil-pds-nginx 330``` 331 332**Alpine:** 333```sh 334rc-service tranquil-pds-nginx restart 335``` 336 337## Cert renewal 338 339Manual mode doesn't auto-renew, so add a renewal job to root's crontab with `crontab -e`. 340 341**Debian:** 342``` 3430 0 * * * podman run --rm -v /srv/tranquil-pds/certs:/etc/letsencrypt:Z -v /srv/tranquil-pds/acme:/var/www/acme:Z docker.io/certbot/certbot:v5.2.2 renew --quiet && systemctl reload tranquil-pds-nginx 344``` 345 346**Alpine:** 347``` 3480 0 * * * podman run --rm -v /srv/tranquil-pds/certs:/etc/letsencrypt -v /srv/tranquil-pds/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service tranquil-pds-nginx restart 349``` 350 351--- 352 353# Verification and maintenance 354 355## Verify installation 356 357```sh 358curl -s https://pds.example.com/xrpc/_health | jq 359curl -s https://pds.example.com/.well-known/atproto-did 360``` 361 362## View logs 363 364**Debian:** 365```bash 366journalctl -u tranquil-pds-app -f 367podman logs -f tranquil-pds-app 368``` 369 370**Alpine:** 371```sh 372rc-service tranquil-pds-app status 373podman logs -f tranquil-pds-app 374``` 375 376## Update Tranquil PDS 377 378Pull the latest image: 379```sh 380podman login atcr.io 381podman pull atcr.io/tranquil.farm/tranquil-pds:latest 382``` 383 384Debian: 385```bash 386systemctl restart tranquil-pds-app 387``` 388 389Alpine: 390```sh 391rc-service tranquil-pds-app restart 392``` 393 394To update a source-built deployment, `git pull` in the repo and rebuild with `podman build -t atcr.io/tranquil.farm/tranquil-pds:latest .` before restarting. 395 396## Backup database 397 398```sh 399podman exec tranquil-pds-db pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql 400``` 401 402## Custom homepage 403 404If a `homepage.html` exists in the app's frontend directory it is served at `/`. The account dashboard stays at `/app/`. The directory defaults to `/var/lib/tranquil-pds/frontend` and is set by `FRONTEND_DIR`. Mount your own into the app container: 405 406```sh 407-v /srv/tranquil-pds/homepage.html:/var/lib/tranquil-pds/frontend/homepage.html:ro,Z 408``` 409 410For ex: 411```html 412<!DOCTYPE html> 413<html> 414<head> 415 <title>Welcome to my PDS</title> 416 <style> 417 body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; } 418 </style> 419</head> 420<body> 421 <h1>Welcome to my dark web popsocket store</h1> 422 <p>This is a <a href="https://atproto.com">AT Protocol</a> Personal Data Server.</p> 423 <p><a href="/app/">Sign in</a> or learn more at <a href="https://bsky.social">Bluesky</a>.</p> 424</body> 425</html> 426```