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