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```