this repo has no description
1# Tranquil PDS Containerized Production Deployment 2> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3This guide covers deploying Tranquil PDS using containers with podman. 4- **Debian 13+**: Uses systemd quadlets (modern, declarative container management) 5- **Alpine 3.23+**: Uses OpenRC service script with podman-compose 6## Prerequisites 7- A VPS with at least 2GB RAM and 20GB disk 8- A domain name pointing to your server's IP 9- A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) 10- Root or sudo access 11## Quick Start (Docker/Podman Compose) 12If you just want to get running quickly: 13```sh 14cp .env.example .env 15``` 16 17Edit `.env` with your values. Generate secrets with `openssl rand -base64 48`. 18 19Build and start: 20```sh 21podman-compose -f docker-compose.prod.yml up -d 22``` 23 24Get initial certificate (after DNS is configured): 25```sh 26podman-compose -f docker-compose.prod.yml run --rm certbot certonly \ 27 --webroot -w /var/www/acme -d pds.example.com 28podman-compose -f docker-compose.prod.yml restart nginx 29``` 30For production setups with proper service management, continue to either the Debian or Alpine section below. 31--- 32# Debian 13+ with Systemd Quadlets 33Quadlets are the modern way to run podman containers under systemd. 34## 1. Install Podman 35```bash 36apt update 37apt install -y podman 38``` 39## 2. Create Directory Structure 40```bash 41mkdir -p /etc/containers/systemd 42mkdir -p /srv/tranquil-pds/{postgres,minio,valkey,certs,acme,config} 43``` 44## 3. Create Environment File 45```bash 46cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env 47chmod 600 /srv/tranquil-pds/config/tranquil-pds.env 48``` 49Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with: 50```bash 51openssl rand -base64 48 52``` 53For quadlets, also add `DATABASE_URL` with the full connection string (systemd doesn't support variable expansion). 54## 4. Install Quadlet Definitions 55Copy the quadlet files from the repository: 56```bash 57cp /opt/tranquil-pds/deploy/quadlets/*.pod /etc/containers/systemd/ 58cp /opt/tranquil-pds/deploy/quadlets/*.container /etc/containers/systemd/ 59``` 60Note: Systemd doesn't support shell-style variable expansion in `Environment=` lines. The quadlet files expect DATABASE_URL to be set in the environment file. 61## 5. Create nginx Configuration 62```bash 63cp /opt/tranquil-pds/deploy/nginx/nginx-quadlet.conf /srv/tranquil-pds/config/nginx.conf 64``` 65## 6. Build Tranquil PDS Image 66```bash 67cd /opt 68git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 69cd tranquil-pds 70podman build -t tranquil-pds:latest . 71``` 72## 7. Create Podman Secrets 73```bash 74source /srv/tranquil-pds/config/tranquil-pds.env 75echo "$DB_PASSWORD" | podman secret create tranquil-pds-db-password - 76echo "$MINIO_ROOT_PASSWORD" | podman secret create tranquil-pds-minio-password - 77``` 78## 8. Start Services and Initialize 79```bash 80systemctl daemon-reload 81systemctl start tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey 82sleep 10 83``` 84 85Create the minio buckets: 86```bash 87podman run --rm --pod tranquil-pds \ 88 -e MINIO_ROOT_USER=minioadmin \ 89 -e MINIO_ROOT_PASSWORD=your-minio-password \ 90 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 91 sh -c "mc alias set local http://localhost:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs && mc mb --ignore-existing local/pds-backups" 92``` 93 94Run migrations: 95```bash 96cargo install sqlx-cli --no-default-features --features postgres 97DATABASE_URL="postgres://tranquil_pds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations 98``` 99## 9. Obtain Wildcard SSL Certificate 100User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. 101 102Create temporary self-signed cert to start services: 103```bash 104openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 105 -keyout /srv/tranquil-pds/certs/privkey.pem \ 106 -out /srv/tranquil-pds/certs/fullchain.pem \ 107 -subj "/CN=pds.example.com" 108systemctl start tranquil-pds-app tranquil-pds-nginx 109``` 110 111Get a wildcard certificate using DNS validation: 112```bash 113podman run --rm -it \ 114 -v /srv/tranquil-pds/certs:/etc/letsencrypt:Z \ 115 docker.io/certbot/certbot:v5.2.2 certonly \ 116 --manual --preferred-challenges dns \ 117 -d pds.example.com -d '*.pds.example.com' \ 118 --agree-tos --email you@example.com 119``` 120Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 121 122For automated renewal, use a DNS provider plugin (e.g., cloudflare, route53). 123 124Link certificates and restart: 125```bash 126ln -sf /srv/tranquil-pds/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/certs/fullchain.pem 127ln -sf /srv/tranquil-pds/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/certs/privkey.pem 128systemctl restart tranquil-pds-nginx 129``` 130## 10. Enable All Services 131```bash 132systemctl enable tranquil-pds-db tranquil-pds-minio tranquil-pds-valkey tranquil-pds-app tranquil-pds-nginx 133``` 134## 11. Configure Firewall 135```bash 136apt install -y ufw 137ufw allow ssh 138ufw allow 80/tcp 139ufw allow 443/tcp 140ufw enable 141``` 142## 12. Certificate Renewal 143Add to root's crontab (`crontab -e`): 144``` 1450 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 146``` 147--- 148# Alpine 3.23+ with OpenRC 149Alpine uses OpenRC, not systemd. We'll use podman-compose with an OpenRC service wrapper. 150## 1. Install Podman 151```sh 152apk update 153apk add podman podman-compose fuse-overlayfs cni-plugins 154rc-update add cgroups 155rc-service cgroups start 156``` 157Enable podman socket for compose: 158```sh 159rc-update add podman 160rc-service podman start 161``` 162## 2. Create Directory Structure 163```sh 164mkdir -p /srv/tranquil-pds/{data,config} 165mkdir -p /srv/tranquil-pds/data/{postgres,minio,valkey,certs,acme} 166``` 167## 3. Clone Repository and Build 168```sh 169cd /opt 170git clone https://tangled.org/lewis.moe/bspds-sandbox tranquil-pds 171cd tranquil-pds 172podman build -t tranquil-pds:latest . 173``` 174## 4. Create Environment File 175```sh 176cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env 177chmod 600 /srv/tranquil-pds/config/tranquil-pds.env 178``` 179Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with: 180```sh 181openssl rand -base64 48 182``` 183## 5. Set Up Compose and nginx 184Copy the production compose and nginx configs: 185```sh 186cp /opt/tranquil-pds/docker-compose.prod.yml /srv/tranquil-pds/docker-compose.yml 187cp /opt/tranquil-pds/nginx.prod.conf /srv/tranquil-pds/config/nginx.conf 188``` 189Edit `/srv/tranquil-pds/docker-compose.yml` to adjust paths if needed: 190- Update volume mounts to use `/srv/tranquil-pds/data/` paths 191- Update nginx cert paths to match `/srv/tranquil-pds/data/certs/` 192Edit `/srv/tranquil-pds/config/nginx.conf` to update cert paths: 193- Change `/etc/nginx/certs/live/${PDS_HOSTNAME}/` to `/etc/nginx/certs/` 194## 6. Create OpenRC Service 195```sh 196cat > /etc/init.d/tranquil-pds << 'EOF' 197#!/sbin/openrc-run 198name="tranquil-pds" 199description="Tranquil PDS AT Protocol PDS (containerized)" 200command="/usr/bin/podman-compose" 201command_args="-f /srv/tranquil-pds/docker-compose.yml up" 202command_background=true 203pidfile="/run/${RC_SVCNAME}.pid" 204directory="/srv/tranquil-pds" 205depend() { 206 need net podman 207 after firewall 208} 209start_pre() { 210 set -a 211 . /srv/tranquil-pds/config/tranquil-pds.env 212 set +a 213} 214stop() { 215 ebegin "Stopping ${name}" 216 cd /srv/tranquil-pds 217 set -a 218 . /srv/tranquil-pds/config/tranquil-pds.env 219 set +a 220 podman-compose -f /srv/tranquil-pds/docker-compose.yml down 221 eend $? 222} 223EOF 224chmod +x /etc/init.d/tranquil-pds 225``` 226## 7. Initialize Services 227Start services: 228```sh 229rc-service tranquil-pds start 230sleep 15 231``` 232 233Create the minio buckets: 234```sh 235source /srv/tranquil-pds/config/tranquil-pds.env 236podman run --rm --network tranquil-pds_default \ 237 -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \ 238 -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \ 239 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 240 sh -c 'mc alias set local http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs && mc mb --ignore-existing local/pds-backups' 241``` 242 243Run migrations: 244```sh 245apk add rustup 246rustup-init -y 247source ~/.cargo/env 248cargo install sqlx-cli --no-default-features --features postgres 249DB_IP=$(podman inspect tranquil-pds-db-1 --format '{{.NetworkSettings.Networks.tranquil-pds_default.IPAddress}}') 250DATABASE_URL="postgres://tranquil_pds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/tranquil-pds/migrations 251``` 252## 8. Obtain Wildcard SSL Certificate 253User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. 254 255Create temporary self-signed cert to start services: 256```sh 257openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 258 -keyout /srv/tranquil-pds/data/certs/privkey.pem \ 259 -out /srv/tranquil-pds/data/certs/fullchain.pem \ 260 -subj "/CN=pds.example.com" 261rc-service tranquil-pds restart 262``` 263 264Get a wildcard certificate using DNS validation: 265```sh 266podman run --rm -it \ 267 -v /srv/tranquil-pds/data/certs:/etc/letsencrypt \ 268 docker.io/certbot/certbot:v5.2.2 certonly \ 269 --manual --preferred-challenges dns \ 270 -d pds.example.com -d '*.pds.example.com' \ 271 --agree-tos --email you@example.com 272``` 273Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 274 275Link certificates and restart: 276```sh 277ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/fullchain.pem /srv/tranquil-pds/data/certs/fullchain.pem 278ln -sf /srv/tranquil-pds/data/certs/live/pds.example.com/privkey.pem /srv/tranquil-pds/data/certs/privkey.pem 279rc-service tranquil-pds restart 280``` 281## 9. Enable Service at Boot 282```sh 283rc-update add tranquil-pds 284``` 285## 10. Configure Firewall 286```sh 287apk add iptables ip6tables 288iptables -A INPUT -p tcp --dport 22 -j ACCEPT 289iptables -A INPUT -p tcp --dport 80 -j ACCEPT 290iptables -A INPUT -p tcp --dport 443 -j ACCEPT 291iptables -A INPUT -i lo -j ACCEPT 292iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 293iptables -P INPUT DROP 294ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT 295ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT 296ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT 297ip6tables -A INPUT -i lo -j ACCEPT 298ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 299ip6tables -P INPUT DROP 300rc-update add iptables 301rc-update add ip6tables 302/etc/init.d/iptables save 303/etc/init.d/ip6tables save 304``` 305## 11. Certificate Renewal 306Add to root's crontab (`crontab -e`): 307``` 3080 0 * * * podman run --rm -v /srv/tranquil-pds/data/certs:/etc/letsencrypt -v /srv/tranquil-pds/data/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service tranquil-pds restart 309``` 310--- 311# Verification and Maintenance 312## Verify Installation 313```sh 314curl -s https://pds.example.com/xrpc/_health | jq 315curl -s https://pds.example.com/.well-known/atproto-did 316``` 317## View Logs 318**Debian:** 319```bash 320journalctl -u tranquil-pds-app -f 321podman logs -f tranquil-pds-app 322``` 323**Alpine:** 324```sh 325podman-compose -f /srv/tranquil-pds/docker-compose.yml logs -f 326podman logs -f tranquil-pds-tranquil-pds-1 327``` 328## Update Tranquil PDS 329```sh 330cd /opt/tranquil-pds 331git pull 332podman build -t tranquil-pds:latest . 333``` 334 335Debian: 336```bash 337systemctl restart tranquil-pds-app 338``` 339 340Alpine: 341```sh 342rc-service tranquil-pds restart 343``` 344## Backup Database 345**Debian:** 346```bash 347podman exec tranquil-pds-db pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql 348``` 349**Alpine:** 350```sh 351podman exec tranquil-pds-db-1 pg_dump -U tranquil_pds pds > /var/backups/pds-$(date +%Y%m%d).sql 352``` 353 354## Custom Homepage 355 356Mount a `homepage.html` into the container's frontend directory and it becomes your landing page. Go nuts with it. Account dashboard is at `/app/` so you won't break anything. 357 358```html 359<!DOCTYPE html> 360<html> 361<head> 362 <title>Welcome to my PDS</title> 363 <style> 364 body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; } 365 </style> 366</head> 367<body> 368 <h1>Welcome to my dark web popsocket store</h1> 369 <p>This is a <a href="https://atproto.com">AT Protocol</a> Personal Data Server.</p> 370 <p><a href="/app/">Sign in</a> or learn more at <a href="https://bsky.social">Bluesky</a>.</p> 371</body> 372</html> 373```