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