WIP! A BB-style forum, on the ATmosphere!
We're still working... we'll be back soon when we have something to show off!
node
typescript
hono
htmx
atproto
1# atBB Deployment Guide
2
3**Version:** 1.0
4**Last Updated:** 2026-02-12
5**Audience:** System administrators deploying atBB to production
6
7> **Related Documentation:**
8> - [docs/trust-model.md](trust-model.md) — Trust model for self-hosted deployment: what the AppView controls, user data guarantees, and security implications
9> - [docs/plans/2026-02-11-deployment-infrastructure-design.md](plans/2026-02-11-deployment-infrastructure-design.md) — Architectural decisions and design rationale behind this deployment approach
10
11## Table of Contents
12
131. [Prerequisites](#1-prerequisites)
142. [Quick Start](#2-quick-start)
153. [Environment Configuration](#3-environment-configuration)
164. [Database Setup](#4-database-setup)
175. [Running the Container](#5-running-the-container)
186. [Reverse Proxy Setup](#6-reverse-proxy-setup)
197. [Monitoring & Logs](#7-monitoring--logs)
208. [Upgrading](#8-upgrading)
219. [Troubleshooting](#9-troubleshooting)
2210. [Docker Compose Example](#10-docker-compose-example)
23
24---
25
26## 1. Prerequisites
27
28Before deploying atBB, ensure you have the following:
29
30### Infrastructure Requirements
31
32- **PostgreSQL 14+**
33 - Managed service recommended: AWS RDS, DigitalOcean Managed Database, Azure Database for PostgreSQL, or similar
34 - Minimum 1GB RAM, 10GB storage (scales with forum size)
35 - SSL/TLS support enabled (`?sslmode=require`)
36 - Database user with CREATE/ALTER/SELECT/INSERT/UPDATE/DELETE permissions
37
38- **Domain Name & DNS**
39 - Registered domain name (e.g., `forum.example.com`)
40 - DNS A/AAAA record pointing to your server's public IP
41 - Recommended: wildcard DNS for future subdomains (`*.forum.example.com`)
42
43- **Container Runtime**
44 - Docker 20.10+ or Docker Desktop
45 - Minimum 512MB RAM allocated to container (1GB+ recommended)
46 - 2GB disk space for container image and logs
47
48### AT Protocol Requirements
49
50**IMPORTANT:** atBB integrates with the AT Protocol network (the decentralized protocol powering Bluesky). You must set up your forum's AT Protocol identity before deployment.
51
52#### 1. Choose a Personal Data Server (PDS)
53
54Your forum needs a PDS to store its records (forum metadata, categories, moderation actions). Options:
55
56- **Self-hosted PDS:** Run your own PDS instance (advanced, recommended for sovereignty)
57 - Guide: https://github.com/bluesky-social/pds
58 - Requires separate server and domain
59 - Full control over data and federation
60
61- **Hosted PDS:** Use Bluesky's PDS (`https://bsky.social`) or another provider
62 - Simpler setup, lower maintenance
63 - Suitable for testing and small forums
64
65#### 2. Create Forum Account
66
67Create an account for your forum on your chosen PDS:
68
69```bash
70# Example with Bluesky PDS
71# Visit https://bsky.app and create account with your forum's handle
72# Handle should match your domain: forum.example.com
73```
74
75**Record these values (you'll need them later):**
76- Forum Handle: `forum.example.com`
77- Forum Password: (choose a strong password, minimum 16 characters)
78- Forum DID: `did:plc:xxxxxxxxxxxxx` (found in account settings or PDS admin interface)
79- PDS URL: `https://bsky.social` (or your PDS URL)
80
81#### 3. Understand Lexicon Namespace
82
83atBB uses the `space.atbb.*` lexicon namespace for its records:
84- `space.atbb.forum.forum` — Forum metadata (name, description, rules)
85- `space.atbb.forum.category` — Forum categories
86- `space.atbb.post` — User posts and replies
87- `space.atbb.membership` — User membership records
88- `space.atbb.modAction` — Moderation actions
89
90Your forum's DID will own the forum-level records, while users' DIDs own their posts and memberships.
91
92### Security Requirements
93
94- **TLS/SSL Certificate:** Let's Encrypt (free) or commercial certificate
95- **Firewall:** Restrict inbound ports to 80/443 only
96- **SSH Access:** Key-based authentication (disable password auth)
97- **Secrets Management:** Secure storage for environment variables (consider cloud secrets manager)
98
99> **Before deploying:** Read [docs/trust-model.md](trust-model.md). It explains what the AppView controls (the Forum DID's credentials and write access), what your users can count on, and the security implications of a compromised server.
100
101---
102
103## 2. Quick Start
104
105Follow these steps for a minimal working deployment. Detailed explanations follow in later sections.
106
107### Step 1: Pull the Docker Image
108
109```bash
110# Pull latest stable version
111docker pull ghcr.io/malpercio-dev/atbb:latest
112
113# Or pin to a specific version (recommended for production)
114docker pull ghcr.io/malpercio-dev/atbb:v1.0.0
115```
116
117Expected output:
118```
119latest: Pulling from malpercio-dev/atbb
120e7c96db7181b: Pull complete
121...
122Status: Downloaded newer image for ghcr.io/malpercio-dev/atbb:latest
123```
124
125### Step 2: Create Environment File
126
127```bash
128# Copy the template
129curl -o .env.production https://raw.githubusercontent.com/malpercio-dev/atbb-monorepo/main/.env.production.example
130
131# Generate a strong session secret
132openssl rand -hex 32
133# Output: a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
134```
135
136**Edit `.env.production` and fill in these REQUIRED values:**
137
138```bash
139# Database connection (from your PostgreSQL provider)
140DATABASE_URL=postgresql://atbb_user:YOUR_DB_PASSWORD@db.example.com:5432/atbb_prod?sslmode=require
141
142# AT Protocol credentials (from Prerequisites step)
143FORUM_DID=did:plc:YOUR_FORUM_DID
144PDS_URL=https://bsky.social
145FORUM_HANDLE=forum.example.com
146FORUM_PASSWORD=YOUR_FORUM_PASSWORD
147
148# OAuth configuration (your public domain)
149OAUTH_PUBLIC_URL=https://forum.example.com
150
151# Session security (use the openssl output from above)
152SESSION_SECRET=a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
153```
154
155**Secure the file:**
156```bash
157chmod 600 .env.production
158```
159
160### Step 3: Run Database Migrations
161
162**CRITICAL:** Run migrations BEFORE starting the application. This creates the database schema.
163
164```bash
165docker run --rm \
166 --env-file .env.production \
167 ghcr.io/malpercio-dev/atbb:latest \
168 pnpm --filter @atbb/appview db:migrate
169```
170
171Expected output:
172```
173> @atbb/db@0.1.0 db:migrate
174> drizzle-kit migrate
175
176Reading migrations from migrations/
177Applying migration: 0000_initial_schema.sql
178Migration applied successfully
179```
180
181**If this fails, DO NOT proceed.** See [Section 4: Database Setup](#4-database-setup) for troubleshooting.
182
183### Step 4: Start the Container
184
185```bash
186docker run -d \
187 --name atbb \
188 --restart unless-stopped \
189 -p 8080:80 \
190 --env-file .env.production \
191 ghcr.io/malpercio-dev/atbb:latest
192```
193
194Options explained:
195- `-d` — Run in background (detached mode)
196- `--name atbb` — Name the container for easy management
197- `--restart unless-stopped` — Auto-restart on crashes or server reboot
198- `-p 8080:80` — Map host port 8080 to container port 80
199- `--env-file .env.production` — Load environment variables
200
201**Verify the container is running:**
202```bash
203docker ps | grep atbb
204# Expected: Container with STATUS "Up X seconds"
205
206docker logs atbb
207# Expected: No errors, services starting
208```
209
210**Test the application:**
211```bash
212curl http://localhost:8080/api/healthz
213# Expected: {"status":"ok"}
214```
215
216### Step 5: Configure Reverse Proxy
217
218**The container is now running on port 8080, but NOT accessible publicly yet.** You need a reverse proxy to:
219- Terminate TLS/SSL (HTTPS)
220- Forward traffic from your domain to the container
221- Handle automatic certificate renewal
222
223**Recommended setup with Caddy (automatic HTTPS):**
224
225Install Caddy:
226```bash
227# Ubuntu/Debian
228sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
229curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
230curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
231sudo apt update
232sudo apt install caddy
233```
234
235Edit `/etc/caddy/Caddyfile`:
236```
237forum.example.com {
238 reverse_proxy localhost:8080
239}
240```
241
242Reload Caddy:
243```bash
244sudo systemctl reload caddy
245```
246
247**Caddy will automatically obtain a Let's Encrypt certificate and enable HTTPS.**
248
249### Step 6: Verify Deployment
250
251Visit your forum: **https://forum.example.com**
252
253Expected: atBB home page loads with no errors.
254
255**If you see errors, proceed to [Section 9: Troubleshooting](#9-troubleshooting).**
256
257---
258
259## 3. Environment Configuration
260
261Complete reference for all environment variables. See `.env.production.example` for detailed comments.
262
263### Required Variables
264
265| Variable | Description | Example |
266|----------|-------------|---------|
267| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/dbname?sslmode=require` |
268| `FORUM_DID` | Forum's AT Protocol DID | `did:plc:abcdef1234567890` |
269| `PDS_URL` | Personal Data Server URL | `https://bsky.social` |
270| `FORUM_HANDLE` | Forum's AT Protocol handle | `forum.example.com` |
271| `FORUM_PASSWORD` | Forum account password | (minimum 16 characters, alphanumeric + symbols) |
272| `OAUTH_PUBLIC_URL` | Public URL for OAuth redirects | `https://forum.example.com` (MUST be HTTPS in production) |
273| `SESSION_SECRET` | Session encryption key | Generate with: `openssl rand -hex 32` |
274
275### Optional Variables
276
277| Variable | Default | Description |
278|----------|---------|-------------|
279| `PORT` | `3000` | AppView API port (internal) |
280| `WEB_PORT` | `3001` | Web UI port (internal) |
281| `APPVIEW_URL` | `http://localhost:3000` | Internal API URL (keep as localhost for single container) |
282| `JETSTREAM_URL` | `wss://jetstream2.us-east.bsky.network/subscribe` | AT Protocol firehose URL |
283| `SESSION_TTL_DAYS` | `30` | Session lifetime in days (1-90 range) |
284| `REDIS_URL` | (none) | Redis connection string (future: multi-instance deployments) |
285
286### Security Best Practices
287
288**SESSION_SECRET Generation:**
289```bash
290# CRITICAL: Never use a predictable value or leave blank
291openssl rand -hex 32
292
293# Use different secrets for dev/staging/production
294# Rotating the secret invalidates all active sessions
295```
296
297**Password Requirements:**
298- Minimum 16 characters
299- Mix of uppercase, lowercase, numbers, symbols
300- Unique per environment (never reuse)
301- Store in password manager or secrets vault
302
303**Connection String Security:**
304```bash
305# Good: SSL/TLS enforced
306DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require
307
308# Bad: Plain text connection (vulnerable to MITM)
309DATABASE_URL=postgresql://user:pass@host:5432/db
310```
311
312**File Permissions:**
313```bash
314# Protect your environment file
315chmod 600 .env.production
316
317# Verify permissions
318ls -la .env.production
319# Expected: -rw------- (read/write for owner only)
320```
321
322### Environment Loading Methods
323
324**Docker CLI:**
325```bash
326# Recommended: Load from file with --init for better signal handling
327docker run --init --env-file .env.production ghcr.io/malpercio-dev/atbb:latest
328
329# Alternative: Individual variables (for orchestrators)
330docker run --init \
331 -e DATABASE_URL="postgresql://..." \
332 -e FORUM_DID="did:plc:..." \
333 -e SESSION_SECRET="..." \
334 ghcr.io/malpercio-dev/atbb:latest
335```
336
337**Note:** The `--init` flag enables tini as PID 1, improving signal handling for graceful shutdown. While not strictly required (the container has its own signal handling), it's considered best practice.
338
339**Docker Compose:**
340```yaml
341services:
342 atbb:
343 image: ghcr.io/malpercio-dev/atbb:latest
344 env_file:
345 - .env.production
346```
347
348**Kubernetes:**
349```yaml
350# Use Secrets (NOT ConfigMaps for sensitive data)
351apiVersion: v1
352kind: Secret
353metadata:
354 name: atbb-secrets
355type: Opaque
356stringData:
357 DATABASE_URL: "postgresql://..."
358 SESSION_SECRET: "..."
359---
360apiVersion: apps/v1
361kind: Deployment
362spec:
363 template:
364 spec:
365 containers:
366 - name: atbb
367 envFrom:
368 - secretRef:
369 name: atbb-secrets
370```
371
372---
373
374## 4. Database Setup
375
376### PostgreSQL Provisioning
377
378#### Option 1: Managed Database (Recommended)
379
380**AWS RDS:**
3811. Navigate to RDS Console → Create Database
3822. Choose PostgreSQL 14+ (latest stable version)
3833. Select appropriate instance size:
384 - Small forum (<1000 users): `db.t3.micro` or `db.t4g.micro`
385 - Medium forum (1000-10000 users): `db.t3.small` or `db.t4g.small`
386 - Large forum (10000+ users): `db.t3.medium` or higher
3874. Enable "Storage Auto Scaling" (start with 20GB)
3885. Enable "Automated Backups" (7-30 day retention)
3896. Enable "Publicly Accessible" only if container is in different VPC
3907. Security group: Allow PostgreSQL (5432) from container's IP/VPC
3918. Create database: `atbb_prod`
3929. Create user: `atbb_user` with generated password
393
394Connection string format:
395```
396postgresql://atbb_user:PASSWORD@instance-name.region.rds.amazonaws.com:5432/atbb_prod?sslmode=require
397```
398
399**DigitalOcean Managed Database:**
4001. Navigate to Databases → Create → PostgreSQL
4012. Choose datacenter closest to your Droplet/container
4023. Select plan (Basic $15/mo sufficient for small forums)
4034. Create database: `atbb_prod`
4045. Create user: `atbb_user` with generated password
4056. Add trusted source: Your Droplet's IP or "All" for simplicity
4067. Download CA certificate (optional, for certificate validation)
407
408Connection string provided in dashboard (copy and use directly).
409
410**Azure Database for PostgreSQL:**
4111. Navigate to Azure Database for PostgreSQL → Create
4122. Choose "Flexible Server" (simpler, cheaper)
4133. Select region and compute tier (Burstable B1ms sufficient for small forums)
4144. Enable "High Availability" for production (optional)
4155. Configure firewall: Add your container's public IP
4166. Create database: `atbb_prod`
417
418Connection string format:
419```
420postgresql://atbb_user@servername:PASSWORD@servername.postgres.database.azure.com:5432/atbb_prod?sslmode=require
421```
422
423#### Option 2: Self-Hosted PostgreSQL
424
425**Installation (Ubuntu/Debian):**
426```bash
427# Install PostgreSQL
428sudo apt update
429sudo apt install -y postgresql postgresql-contrib
430
431# Start and enable service
432sudo systemctl enable postgresql
433sudo systemctl start postgresql
434```
435
436**Create database and user:**
437```bash
438sudo -u postgres psql
439
440-- In psql prompt:
441CREATE DATABASE atbb_prod;
442CREATE USER atbb_user WITH PASSWORD 'YOUR_STRONG_PASSWORD';
443GRANT ALL PRIVILEGES ON DATABASE atbb_prod TO atbb_user;
444\q
445```
446
447**Enable remote connections (if container is on different host):**
448
449Edit `/etc/postgresql/14/main/postgresql.conf`:
450```
451listen_addresses = '*' # Or specific IP
452```
453
454Edit `/etc/postgresql/14/main/pg_hba.conf`:
455```
456# Add this line (replace 0.0.0.0/0 with specific IP range in production)
457host atbb_prod atbb_user 0.0.0.0/0 scram-sha-256
458```
459
460Restart PostgreSQL:
461```bash
462sudo systemctl restart postgresql
463```
464
465Connection string:
466```
467postgresql://atbb_user:YOUR_STRONG_PASSWORD@your-server-ip:5432/atbb_prod
468```
469
470### Running Database Migrations
471
472Migrations create the database schema (tables, indexes, constraints).
473
474**First-time setup:**
475```bash
476docker run --rm \
477 --env-file .env.production \
478 ghcr.io/malpercio-dev/atbb:latest \
479 pnpm --filter @atbb/appview db:migrate
480```
481
482Options explained:
483- `--rm` — Remove container after migration completes
484- `--env-file .env.production` — Load database connection string
485- `pnpm --filter @atbb/appview db:migrate` — Run Drizzle migrations
486
487**Expected output (success):**
488```
489Reading migrations from /app/packages/db/migrations
490Applying migration: 0000_initial_schema.sql
491Applying migration: 0001_add_deleted_flag.sql
492All migrations applied successfully
493```
494
495**Verify migrations:**
496```bash
497# Connect to your database
498psql "postgresql://atbb_user:PASSWORD@host:5432/atbb_prod?sslmode=require"
499
500# List tables
501\dt
502
503# Expected output:
504# Schema | Name | Type | Owner
505# --------+-------------------+-------+-----------
506# public | categories | table | atbb_user
507# public | firehose_cursor | table | atbb_user
508# public | forums | table | atbb_user
509# public | memberships | table | atbb_user
510# public | mod_actions | table | atbb_user
511# public | posts | table | atbb_user
512# public | users | table | atbb_user
513```
514
515### Migration Troubleshooting
516
517**Error: "database does not exist"**
518```
519FATAL: database "atbb_prod" does not exist
520```
521
522Solution: Create the database first (see self-hosted instructions above, or create via cloud console).
523
524**Error: "password authentication failed"**
525```
526FATAL: password authentication failed for user "atbb_user"
527```
528
529Solution: Verify credentials in `DATABASE_URL` match database user.
530
531**Error: "connection refused"**
532```
533Error: connect ECONNREFUSED
534```
535
536Solution:
537- Check database host/port are correct
538- Verify firewall allows connections from container's IP
539- For cloud databases, ensure "trusted sources" includes your IP
540
541**Error: "SSL connection required"**
542```
543FATAL: no pg_hba.conf entry for host, SSL off
544```
545
546Solution: Add `?sslmode=require` to connection string.
547
548**Error: "permission denied for schema public"**
549```
550ERROR: permission denied for schema public
551```
552
553Solution: Grant schema permissions:
554```sql
555GRANT USAGE ON SCHEMA public TO atbb_user;
556GRANT CREATE ON SCHEMA public TO atbb_user;
557```
558
559---
560
561## 5. Running the Container
562
563### Basic Deployment
564
565**Production command (recommended):**
566```bash
567docker run -d \
568 --name atbb \
569 --restart unless-stopped \
570 -p 8080:80 \
571 --env-file .env.production \
572 ghcr.io/malpercio-dev/atbb:latest
573```
574
575**Pin to specific version (recommended for stability):**
576```bash
577docker run -d \
578 --name atbb \
579 --restart unless-stopped \
580 -p 8080:80 \
581 --env-file .env.production \
582 ghcr.io/malpercio-dev/atbb:v1.0.0
583```
584
585**Pin to specific commit SHA (for rollback/testing):**
586```bash
587docker run -d \
588 --name atbb \
589 --restart unless-stopped \
590 -p 8080:80 \
591 --env-file .env.production \
592 ghcr.io/malpercio-dev/atbb:main-a1b2c3d
593```
594
595### Advanced Options
596
597**Custom port mapping:**
598```bash
599# Expose on different host port
600docker run -d \
601 --name atbb \
602 -p 3000:80 \
603 --env-file .env.production \
604 ghcr.io/malpercio-dev/atbb:latest
605
606# Bind to specific interface (localhost only)
607docker run -d \
608 --name atbb \
609 -p 127.0.0.1:8080:80 \
610 --env-file .env.production \
611 ghcr.io/malpercio-dev/atbb:latest
612```
613
614**Resource limits:**
615```bash
616docker run -d \
617 --name atbb \
618 --restart unless-stopped \
619 -p 8080:80 \
620 --memory="1g" \
621 --cpus="1.0" \
622 --env-file .env.production \
623 ghcr.io/malpercio-dev/atbb:latest
624```
625
626**Custom network:**
627```bash
628# Create network
629docker network create atbb-network
630
631# Run with network
632docker run -d \
633 --name atbb \
634 --network atbb-network \
635 -p 8080:80 \
636 --env-file .env.production \
637 ghcr.io/malpercio-dev/atbb:latest
638```
639
640### Container Management
641
642**View logs:**
643```bash
644# All logs
645docker logs atbb
646
647# Follow logs (live)
648docker logs -f atbb
649
650# Last 100 lines
651docker logs --tail 100 atbb
652
653# Logs since timestamp
654docker logs --since 2026-02-12T10:00:00 atbb
655```
656
657**Stop container:**
658```bash
659docker stop atbb
660```
661
662**Start stopped container:**
663```bash
664docker start atbb
665```
666
667**Restart container:**
668```bash
669docker restart atbb
670```
671
672**Remove container:**
673```bash
674# Stop first
675docker stop atbb
676
677# Remove
678docker rm atbb
679```
680
681**Execute commands inside container (debugging):**
682```bash
683# Interactive shell
684docker exec -it atbb sh
685
686# Run single command
687docker exec atbb ps aux
688docker exec atbb df -h
689docker exec atbb cat /etc/nginx/nginx.conf
690```
691
692### Health Checks
693
694The container exposes a health endpoint:
695
696**Check via curl:**
697```bash
698curl http://localhost:8080/api/healthz
699```
700
701**Expected response:**
702```json
703{"status":"ok"}
704```
705
706**Check via Docker:**
707```bash
708docker inspect atbb | grep -A 5 Health
709```
710
711**Use in monitoring scripts:**
712```bash
713#!/bin/bash
714# health-check.sh
715
716HEALTH=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/healthz)
717
718if [ "$HEALTH" != "200" ]; then
719 echo "ALERT: atBB health check failed (HTTP $HEALTH)"
720 # Send alert (email, Slack, PagerDuty, etc.)
721 exit 1
722fi
723
724echo "OK: atBB is healthy"
725exit 0
726```
727
728**Run as cron job:**
729```bash
730# Check every 5 minutes
731*/5 * * * * /path/to/health-check.sh >> /var/log/atbb-health.log 2>&1
732```
733
734---
735
736## 6. Reverse Proxy Setup
737
738The container exposes HTTP on port 80. In production, you need a reverse proxy to:
739- Terminate TLS/SSL (enable HTTPS)
740- Manage domain routing
741- Handle certificate renewal
742- Provide additional security headers
743
744### Caddy (Recommended)
745
746**Why Caddy:**
747- Automatic HTTPS with Let's Encrypt (zero configuration)
748- Simple configuration syntax
749- Auto-renewal of certificates
750- Modern defaults (HTTP/2, security headers)
751
752**Installation:**
753
754Ubuntu/Debian:
755```bash
756sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
757curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
758curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
759sudo apt update
760sudo apt install caddy
761```
762
763CentOS/RHEL:
764```bash
765dnf install 'dnf-command(copr)'
766dnf copr enable @caddy/caddy
767dnf install caddy
768```
769
770**Basic Configuration:**
771
772Edit `/etc/caddy/Caddyfile`:
773```
774forum.example.com {
775 reverse_proxy localhost:8080
776}
777```
778
779**Advanced Configuration (with security headers):**
780
781```
782forum.example.com {
783 # Reverse proxy to atBB container
784 reverse_proxy localhost:8080
785
786 # Security headers
787 header {
788 # Enable HSTS (force HTTPS)
789 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
790
791 # Prevent clickjacking
792 X-Frame-Options "SAMEORIGIN"
793
794 # Prevent MIME sniffing
795 X-Content-Type-Options "nosniff"
796
797 # XSS protection
798 X-XSS-Protection "1; mode=block"
799
800 # Referrer policy
801 Referrer-Policy "strict-origin-when-cross-origin"
802
803 # Content Security Policy (adjust as needed)
804 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
805 }
806
807 # Access logs
808 log {
809 output file /var/log/caddy/atbb-access.log
810 format json
811 }
812}
813```
814
815**Apply configuration:**
816```bash
817# Validate configuration
818sudo caddy validate --config /etc/caddy/Caddyfile
819
820# Reload Caddy (no downtime)
821sudo systemctl reload caddy
822
823# Check status
824sudo systemctl status caddy
825```
826
827**Verify HTTPS:**
828```bash
829curl -I https://forum.example.com
830# Expected: HTTP/2 200 with security headers
831```
832
833### nginx
834
835**Installation:**
836```bash
837sudo apt install -y nginx
838```
839
840**Configuration:**
841
842Create `/etc/nginx/sites-available/atbb`:
843```nginx
844# HTTP -> HTTPS redirect
845server {
846 listen 80;
847 listen [::]:80;
848 server_name forum.example.com;
849 return 301 https://$server_name$request_uri;
850}
851
852# HTTPS server
853server {
854 listen 443 ssl http2;
855 listen [::]:443 ssl http2;
856 server_name forum.example.com;
857
858 # SSL certificates (obtain via certbot)
859 ssl_certificate /etc/letsencrypt/live/forum.example.com/fullchain.pem;
860 ssl_certificate_key /etc/letsencrypt/live/forum.example.com/privkey.pem;
861 ssl_trusted_certificate /etc/letsencrypt/live/forum.example.com/chain.pem;
862
863 # SSL settings (Mozilla Modern configuration)
864 ssl_protocols TLSv1.3;
865 ssl_prefer_server_ciphers off;
866 ssl_session_timeout 1d;
867 ssl_session_cache shared:SSL:10m;
868 ssl_session_tickets off;
869
870 # Security headers
871 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
872 add_header X-Frame-Options "SAMEORIGIN" always;
873 add_header X-Content-Type-Options "nosniff" always;
874 add_header X-XSS-Protection "1; mode=block" always;
875
876 # Proxy to atBB container
877 location / {
878 proxy_pass http://127.0.0.1:8080;
879 proxy_http_version 1.1;
880 proxy_set_header Host $host;
881 proxy_set_header X-Real-IP $remote_addr;
882 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
883 proxy_set_header X-Forwarded-Proto $scheme;
884
885 # WebSocket support (for future features)
886 proxy_set_header Upgrade $http_upgrade;
887 proxy_set_header Connection "upgrade";
888 }
889
890 # Access logs
891 access_log /var/log/nginx/atbb-access.log combined;
892 error_log /var/log/nginx/atbb-error.log;
893}
894```
895
896**Obtain SSL certificate with Certbot:**
897```bash
898# Install Certbot
899sudo apt install -y certbot python3-certbot-nginx
900
901# Obtain certificate (interactive)
902sudo certbot --nginx -d forum.example.com
903
904# Certbot will automatically:
905# - Validate domain ownership
906# - Obtain certificate from Let's Encrypt
907# - Update nginx configuration
908# - Set up auto-renewal
909```
910
911**Enable site:**
912```bash
913sudo ln -s /etc/nginx/sites-available/atbb /etc/nginx/sites-enabled/
914sudo nginx -t # Test configuration
915sudo systemctl reload nginx
916```
917
918### Traefik
919
920**docker-compose.yml with Traefik:**
921```yaml
922version: '3.8'
923
924services:
925 traefik:
926 image: traefik:v2.11
927 command:
928 - "--providers.docker=true"
929 - "--entrypoints.web.address=:80"
930 - "--entrypoints.websecure.address=:443"
931 - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
932 - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
933 - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
934 ports:
935 - "80:80"
936 - "443:443"
937 volumes:
938 - "/var/run/docker.sock:/var/run/docker.sock:ro"
939 - "./letsencrypt:/letsencrypt"
940
941 atbb:
942 image: ghcr.io/malpercio-dev/atbb:latest
943 env_file:
944 - .env.production
945 labels:
946 - "traefik.enable=true"
947 - "traefik.http.routers.atbb.rule=Host(`forum.example.com`)"
948 - "traefik.http.routers.atbb.entrypoints=websecure"
949 - "traefik.http.routers.atbb.tls.certresolver=letsencrypt"
950 - "traefik.http.services.atbb.loadbalancer.server.port=80"
951```
952
953Start with:
954```bash
955docker-compose up -d
956```
957
958---
959
960## 7. Monitoring & Logs
961
962### Container Logs
963
964**View logs:**
965```bash
966# All logs
967docker logs atbb
968
969# Follow logs (real-time)
970docker logs -f atbb
971
972# Filter by timestamp
973docker logs --since 2026-02-12T10:00:00 atbb
974docker logs --until 2026-02-12T12:00:00 atbb
975```
976
977**Log format:** JSON structured logs
978
979Example log entry:
980```json
981{
982 "level": "info",
983 "time": "2026-02-12T14:30:00.000Z",
984 "service": "appview",
985 "msg": "HTTP request",
986 "method": "GET",
987 "path": "/api/forum",
988 "status": 200,
989 "duration": 15
990}
991```
992
993**Parse logs with jq:**
994```bash
995# Filter by level
996docker logs atbb | grep '^{' | jq 'select(.level == "error")'
997
998# Extract errors from last hour
999docker logs --since 1h atbb | grep '^{' | jq 'select(.level == "error")'
1000
1001# Count requests by path
1002docker logs atbb | grep '^{' | jq -r '.path' | sort | uniq -c | sort -nr
1003```
1004
1005### Log Persistence
1006
1007**Forward to log aggregator:**
1008
1009Using Docker logging driver (syslog):
1010```bash
1011docker run -d \
1012 --name atbb \
1013 --log-driver syslog \
1014 --log-opt syslog-address=udp://logserver:514 \
1015 --log-opt tag="atbb" \
1016 -p 8080:80 \
1017 --env-file .env.production \
1018 ghcr.io/malpercio-dev/atbb:latest
1019```
1020
1021Using Docker logging driver (json-file with rotation):
1022```bash
1023docker run -d \
1024 --name atbb \
1025 --log-driver json-file \
1026 --log-opt max-size=10m \
1027 --log-opt max-file=3 \
1028 -p 8080:80 \
1029 --env-file .env.production \
1030 ghcr.io/malpercio-dev/atbb:latest
1031```
1032
1033### Health Monitoring
1034
1035**Health endpoint:** `GET /api/healthz`
1036
1037Example monitoring script (save as `/usr/local/bin/atbb-health-check`):
1038```bash
1039#!/bin/bash
1040# atbb-health-check - Monitor atBB health and restart if needed
1041
1042CONTAINER_NAME="atbb"
1043HEALTH_URL="http://localhost:8080/api/healthz"
1044MAX_FAILURES=3
1045
1046FAILURES=0
1047
1048while true; do
1049 # Check health endpoint
1050 HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL")
1051
1052 if [ "$HTTP_CODE" != "200" ]; then
1053 FAILURES=$((FAILURES + 1))
1054 echo "$(date): Health check failed (HTTP $HTTP_CODE), failures: $FAILURES/$MAX_FAILURES"
1055
1056 if [ "$FAILURES" -ge "$MAX_FAILURES" ]; then
1057 echo "$(date): Max failures reached, restarting container"
1058 docker restart "$CONTAINER_NAME"
1059 FAILURES=0
1060 sleep 60 # Wait for restart
1061 fi
1062 else
1063 # Reset failure counter on success
1064 if [ "$FAILURES" -gt 0 ]; then
1065 echo "$(date): Health check recovered"
1066 fi
1067 FAILURES=0
1068 fi
1069
1070 sleep 60 # Check every minute
1071done
1072```
1073
1074Run as systemd service:
1075```bash
1076sudo chmod +x /usr/local/bin/atbb-health-check
1077
1078cat <<EOF | sudo tee /etc/systemd/system/atbb-health-check.service
1079[Unit]
1080Description=atBB Health Check Monitor
1081After=docker.service
1082Requires=docker.service
1083
1084[Service]
1085Type=simple
1086ExecStart=/usr/local/bin/atbb-health-check
1087Restart=always
1088StandardOutput=append:/var/log/atbb-health-check.log
1089StandardError=append:/var/log/atbb-health-check.log
1090
1091[Install]
1092WantedBy=multi-user.target
1093EOF
1094
1095sudo systemctl daemon-reload
1096sudo systemctl enable atbb-health-check
1097sudo systemctl start atbb-health-check
1098```
1099
1100### Resource Monitoring
1101
1102**Monitor container resource usage:**
1103```bash
1104# Real-time stats
1105docker stats atbb
1106
1107# Example output:
1108# CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
1109# atbb 2.5% 256MiB / 1GiB 25% 1.2MB/5MB 0B/0B
1110```
1111
1112**Set up alerts for resource limits:**
1113```bash
1114#!/bin/bash
1115# atbb-resource-alert - Alert on high resource usage
1116
1117CONTAINER="atbb"
1118CPU_THRESHOLD=80
1119MEM_THRESHOLD=80
1120
1121STATS=$(docker stats --no-stream --format "{{.CPUPerc}},{{.MemPerc}}" "$CONTAINER")
1122CPU=$(echo "$STATS" | cut -d',' -f1 | tr -d '%')
1123MEM=$(echo "$STATS" | cut -d',' -f2 | tr -d '%')
1124
1125if [ "$(echo "$CPU > $CPU_THRESHOLD" | bc)" -eq 1 ]; then
1126 echo "ALERT: CPU usage is ${CPU}% (threshold: ${CPU_THRESHOLD}%)"
1127 # Send notification (email, Slack, etc.)
1128fi
1129
1130if [ "$(echo "$MEM > $MEM_THRESHOLD" | bc)" -eq 1 ]; then
1131 echo "ALERT: Memory usage is ${MEM}% (threshold: ${MEM_THRESHOLD}%)"
1132 # Send notification
1133fi
1134```
1135
1136### Future: Observability
1137
1138Planned enhancements (not yet implemented):
1139- Prometheus metrics endpoint (`/api/metrics`)
1140- OpenTelemetry tracing
1141- Grafana dashboard templates
1142- Alert manager integration
1143
1144---
1145
1146## 8. Upgrading
1147
1148### Upgrade Process
1149
1150**IMPORTANT:** Upgrading will cause brief downtime (sessions are stored in memory and will be lost).
1151
1152**Step 1: Check release notes**
1153```bash
1154# View releases on GitHub
1155# https://github.com/malpercio-dev/atbb-monorepo/releases
1156
1157# Look for:
1158# - Breaking changes
1159# - Database migration requirements
1160# - New environment variables
1161```
1162
1163**Step 2: Backup database**
1164```bash
1165# Backup current database (critical!)
1166pg_dump "postgresql://atbb_user:PASSWORD@host:5432/atbb_prod?sslmode=require" \
1167 > atbb_backup_$(date +%Y%m%d_%H%M%S).sql
1168
1169# Verify backup
1170ls -lh atbb_backup_*.sql
1171```
1172
1173**Step 3: Pull new image**
1174```bash
1175# Pull specific version
1176docker pull ghcr.io/malpercio-dev/atbb:v1.1.0
1177
1178# Or pull latest
1179docker pull ghcr.io/malpercio-dev/atbb:latest
1180```
1181
1182**Step 4: Run migrations (if required)**
1183```bash
1184# Check release notes for migration requirements
1185# If migrations are needed:
1186docker run --rm \
1187 --env-file .env.production \
1188 ghcr.io/malpercio-dev/atbb:v1.1.0 \
1189 pnpm --filter @atbb/appview db:migrate
1190```
1191
1192**Step 5: Stop old container**
1193```bash
1194docker stop atbb
1195docker rm atbb
1196```
1197
1198**Step 6: Start new container**
1199```bash
1200docker run -d \
1201 --name atbb \
1202 --restart unless-stopped \
1203 -p 8080:80 \
1204 --env-file .env.production \
1205 ghcr.io/malpercio-dev/atbb:v1.1.0
1206```
1207
1208**Step 7: Verify upgrade**
1209```bash
1210# Check logs for errors
1211docker logs atbb
1212
1213# Test health endpoint
1214curl http://localhost:8080/api/healthz
1215
1216# Visit forum in browser
1217# Test key functionality (login, post, etc.)
1218```
1219
1220### Rollback Procedure
1221
1222If upgrade fails, rollback to previous version:
1223
1224**Step 1: Stop broken container**
1225```bash
1226docker stop atbb
1227docker rm atbb
1228```
1229
1230**Step 2: Restore database (if migrations were run)**
1231```bash
1232# Connect to database
1233psql "postgresql://atbb_user:PASSWORD@host:5432/atbb_prod?sslmode=require"
1234
1235# Drop all tables
1236DROP SCHEMA public CASCADE;
1237CREATE SCHEMA public;
1238
1239# Restore from backup
1240psql "postgresql://atbb_user:PASSWORD@host:5432/atbb_prod?sslmode=require" \
1241 < atbb_backup_20260212_140000.sql
1242```
1243
1244**Step 3: Start old version**
1245```bash
1246docker run -d \
1247 --name atbb \
1248 --restart unless-stopped \
1249 -p 8080:80 \
1250 --env-file .env.production \
1251 ghcr.io/malpercio-dev/atbb:v1.0.0
1252```
1253
1254### Zero-Downtime Upgrades (Future)
1255
1256Once Redis session storage is implemented, you can upgrade with zero downtime:
1257
12581. Start new container on different port
12592. Test new version
12603. Switch reverse proxy to new port
12614. Stop old container
1262
1263**Not currently supported** because sessions are in-memory.
1264
1265---
1266
1267## 9. Troubleshooting
1268
1269### Container Won't Start
1270
1271**Symptom:** Container exits immediately after starting
1272
1273**Diagnosis:**
1274```bash
1275docker logs atbb
1276```
1277
1278**Common causes:**
1279
12801. **Missing environment variables**
1281 ```
1282 Error: DATABASE_URL is required
1283 ```
1284 Solution: Verify `.env.production` has all required variables (see Section 3).
1285
12862. **Database connection failed**
1287 ```
1288 Error: connect ECONNREFUSED
1289 ```
1290 Solution:
1291 - Verify `DATABASE_URL` is correct
1292 - Check firewall allows connections from container's IP
1293 - Test connection manually: `psql "postgresql://..."`
1294
12953. **Port already in use**
1296 ```
1297 Error: bind: address already in use
1298 ```
1299 Solution: Change host port mapping: `-p 8081:80`
1300
13014. **Migrations not run**
1302 ```
1303 Error: relation "forums" does not exist
1304 ```
1305 Solution: Run migrations (Section 4).
1306
1307### Database Connection Issues
1308
1309**Symptom:** Application starts but fails on database queries
1310
1311**Error examples:**
1312```
1313FATAL: password authentication failed for user "atbb_user"
1314FATAL: no pg_hba.conf entry for host, SSL off
1315Error: connect ETIMEDOUT
1316```
1317
1318**Solutions:**
1319
13201. **Test connection manually:**
1321 ```bash
1322 psql "postgresql://atbb_user:PASSWORD@host:5432/atbb_prod?sslmode=require"
1323 ```
1324 If this fails, the issue is NOT with atBB (fix database access first).
1325
13262. **Check credentials:**
1327 - Verify username/password in `DATABASE_URL`
1328 - Ensure user has been created in database
1329
13303. **Check SSL settings:**
1331 ```bash
1332 # If database requires SSL, ensure connection string includes:
1333 DATABASE_URL=postgresql://...?sslmode=require
1334 ```
1335
13364. **Check network/firewall:**
1337 - Verify container can reach database host
1338 - Test from within container: `docker exec atbb ping db.example.com`
1339 - Check cloud provider security groups/firewall rules
1340
1341### OAuth Redirect URI Mismatch
1342
1343**Symptom:** Login fails with "redirect URI mismatch" error
1344
1345**Cause:** `OAUTH_PUBLIC_URL` doesn't match the actual domain users access
1346
1347**Solution:**
1348
13491. Verify `OAUTH_PUBLIC_URL` in `.env.production`:
1350 ```bash
1351 OAUTH_PUBLIC_URL=https://forum.example.com # Must match actual domain
1352 ```
1353
13542. Common mistakes:
1355 - ❌ `http://` instead of `https://` (use HTTPS in production)
1356 - ❌ Trailing slash: `https://forum.example.com/` (remove trailing slash)
1357 - ❌ Wrong subdomain: `https://www.forum.example.com` vs `https://forum.example.com`
1358
13593. Restart container after fixing:
1360 ```bash
1361 docker restart atbb
1362 ```
1363
1364### PDS Connectivity Problems
1365
1366**Symptom:** Cannot create posts, forum metadata not syncing
1367
1368**Error in logs:**
1369```
1370Error: Failed to connect to PDS: ENOTFOUND
1371Error: Invalid credentials for FORUM_HANDLE
1372```
1373
1374**Solutions:**
1375
13761. **Verify PDS URL:**
1377 ```bash
1378 curl https://bsky.social/xrpc/_health
1379 # Should return: {"version":"0.x.x"}
1380 ```
1381
13822. **Test forum credentials:**
1383 ```bash
1384 # Use atproto CLI or curl to test auth
1385 curl -X POST https://bsky.social/xrpc/com.atproto.server.createSession \
1386 -H "Content-Type: application/json" \
1387 -d '{
1388 "identifier": "forum.example.com",
1389 "password": "YOUR_FORUM_PASSWORD"
1390 }'
1391 # Should return: {"did":"did:plc:...","accessJwt":"..."}
1392 ```
1393
13943. **Check environment variables:**
1395 ```bash
1396 docker exec atbb env | grep -E 'FORUM_|PDS_'
1397 # Verify all values are correct
1398 ```
1399
1400### High Memory Usage
1401
1402**Symptom:** Container using excessive memory (>1GB)
1403
1404**Diagnosis:**
1405```bash
1406docker stats atbb
1407```
1408
1409**Solutions:**
1410
14111. **Set memory limit:**
1412 ```bash
1413 docker update --memory="512m" atbb
1414 ```
1415
14162. **Check for memory leak:**
1417 - Monitor over time: `docker stats atbb`
1418 - If memory grows continuously, report issue with logs
1419
14203. **Increase container memory:**
1421 ```bash
1422 # For large forums, 1-2GB may be normal
1423 docker update --memory="2g" atbb
1424 ```
1425
1426### Logs Filling Disk
1427
1428**Symptom:** Disk space running out due to large log files
1429
1430**Check log size:**
1431```bash
1432du -sh /var/lib/docker/containers/*/
1433```
1434
1435**Solutions:**
1436
14371. **Configure log rotation (recommended):**
1438 ```bash
1439 # Stop container
1440 docker stop atbb
1441 docker rm atbb
1442
1443 # Restart with log rotation
1444 docker run -d \
1445 --name atbb \
1446 --log-opt max-size=10m \
1447 --log-opt max-file=3 \
1448 -p 8080:80 \
1449 --env-file .env.production \
1450 ghcr.io/malpercio-dev/atbb:latest
1451 ```
1452
14532. **Manually clean logs:**
1454 ```bash
1455 # Truncate logs (preserves container)
1456 truncate -s 0 $(docker inspect --format='{{.LogPath}}' atbb)
1457 ```
1458
14593. **Use external log aggregator** (syslog, fluentd, etc.)
1460
1461### Container Performance Issues
1462
1463**Symptom:** Slow response times, high CPU usage
1464
1465**Diagnosis:**
1466```bash
1467docker stats atbb
1468docker top atbb
1469```
1470
1471**Solutions:**
1472
14731. **Check database performance:**
1474 - Slow queries often bottleneck at database
1475 - Monitor database server metrics
1476 - Add indexes if needed (consult forum performance guide)
1477
14782. **Increase resources:**
1479 ```bash
1480 docker update --cpus="2.0" --memory="1g" atbb
1481 ```
1482
14833. **Check reverse proxy settings:**
1484 - Ensure proxy is not buffering excessively
1485 - Verify HTTP/2 is enabled for better performance
1486
14874. **Monitor specific endpoints:**
1488 ```bash
1489 # Extract slow requests from logs
1490 docker logs atbb | grep '^{' | jq 'select(.duration > 1000)'
1491 ```
1492
1493### Session Errors / Random Logouts
1494
1495**Symptom:** Users randomly logged out, "session expired" errors
1496
1497**Causes:**
1498
14991. **Container restarted** — Sessions are in-memory, lost on restart
15002. **SESSION_SECRET changed** — Invalidates all sessions
15013. **SESSION_SECRET not set** — Each restart generates new secret
1502
1503**Solutions:**
1504
15051. **Verify SESSION_SECRET is set:**
1506 ```bash
1507 docker exec atbb env | grep SESSION_SECRET
1508 # Should show a 64-character hex string
1509 ```
1510
15112. **If blank, generate and set:**
1512 ```bash
1513 openssl rand -hex 32
1514 # Add to .env.production
1515 # Restart container
1516 ```
1517
15183. **Future:** Use Redis for persistent sessions (not yet implemented)
1519
1520### Getting Help
1521
1522If you cannot resolve an issue:
1523
15241. **Collect diagnostics:**
1525 ```bash
1526 # Container logs
1527 docker logs atbb > atbb-logs.txt
1528
1529 # Container info
1530 docker inspect atbb > atbb-inspect.json
1531
1532 # Resource usage
1533 docker stats --no-stream atbb
1534 ```
1535
15362. **Sanitize sensitive data:**
1537 - Remove passwords from logs
1538 - Remove `SESSION_SECRET` from environment dumps
1539
15403. **Report issue:**
1541 - GitHub Issues: https://github.com/malpercio-dev/atbb-monorepo/issues
1542 - Include: atBB version, error messages, steps to reproduce
1543 - Attach sanitized logs
1544
1545---
1546
1547## 10. Docker Compose Example
1548
1549For simpler local testing or single-server deployments, use Docker Compose.
1550
1551**File:** `docker-compose.example.yml` (included in repository)
1552
1553### What It Provides
1554
1555- PostgreSQL database (local development)
1556- atBB application container
1557- Automatic dependency management (atBB waits for PostgreSQL)
1558- Volume persistence for database
1559- Health checks
1560
1561### Usage
1562
1563**Step 1: Download files**
1564```bash
1565# Download docker-compose.example.yml
1566curl -O https://raw.githubusercontent.com/malpercio-dev/atbb-monorepo/main/docker-compose.example.yml
1567
1568# Download .env.production.example
1569curl -O https://raw.githubusercontent.com/malpercio-dev/atbb-monorepo/main/.env.production.example
1570
1571# Rename to .env
1572mv .env.production.example .env
1573```
1574
1575**Step 2: Configure environment**
1576```bash
1577# Generate session secret
1578openssl rand -hex 32
1579
1580# Edit .env and fill in:
1581nano .env
1582```
1583
1584Required changes in `.env`:
1585```bash
1586# AT Protocol credentials (from Prerequisites)
1587FORUM_DID=did:plc:YOUR_FORUM_DID
1588PDS_URL=https://bsky.social
1589FORUM_HANDLE=forum.example.com
1590FORUM_PASSWORD=YOUR_FORUM_PASSWORD
1591
1592# OAuth (for local testing, use http://localhost)
1593OAUTH_PUBLIC_URL=http://localhost
1594
1595# Session secret (generated above)
1596SESSION_SECRET=a1b2c3d4e5f6...
1597
1598# Database connection will be set by docker-compose
1599# (Uses container name "postgres" as hostname)
1600```
1601
1602**Step 3: Start services**
1603```bash
1604docker-compose -f docker-compose.example.yml up -d
1605```
1606
1607Expected output:
1608```
1609Creating network "atbb_default" with the default driver
1610Creating volume "atbb_postgres_data" with default driver
1611Creating atbb-postgres ... done
1612Creating atbb-app ... done
1613```
1614
1615**Step 4: Run migrations**
1616```bash
1617docker-compose -f docker-compose.example.yml exec atbb \
1618 pnpm --filter @atbb/appview db:migrate
1619```
1620
1621**Step 5: Access forum**
1622
1623Visit: **http://localhost**
1624
1625### Management Commands
1626
1627**View logs:**
1628```bash
1629# All services
1630docker-compose -f docker-compose.example.yml logs -f
1631
1632# Specific service
1633docker-compose -f docker-compose.example.yml logs -f atbb
1634docker-compose -f docker-compose.example.yml logs -f postgres
1635```
1636
1637**Stop services:**
1638```bash
1639docker-compose -f docker-compose.example.yml down
1640```
1641
1642**Stop and remove data:**
1643```bash
1644docker-compose -f docker-compose.example.yml down -v
1645# WARNING: This deletes the database volume!
1646```
1647
1648**Restart services:**
1649```bash
1650docker-compose -f docker-compose.example.yml restart
1651```
1652
1653**Upgrade to new version:**
1654```bash
1655# Pull new image
1656docker-compose -f docker-compose.example.yml pull atbb
1657
1658# Run migrations (if required by release notes)
1659docker-compose -f docker-compose.example.yml exec atbb \
1660 pnpm --filter @atbb/appview db:migrate
1661
1662# Restart
1663docker-compose -f docker-compose.example.yml restart atbb
1664```
1665
1666### Production Considerations
1667
1668**DO NOT use docker-compose.example.yml as-is in production.**
1669
1670Limitations:
1671- Database password is weak (change in compose file)
1672- No TLS/SSL for database
1673- No backups configured
1674- Single-server only
1675
1676**For production:**
16771. Use managed PostgreSQL (AWS RDS, DigitalOcean, etc.)
16782. Run atBB container separately (not with local PostgreSQL)
16793. Set up reverse proxy with HTTPS (Caddy/nginx)
16804. Use strong passwords and secrets
16815. Configure automated backups
16826. Set up monitoring and alerting
1683
1684**Modified compose for production (atBB only, external DB):**
1685```yaml
1686version: '3.8'
1687
1688services:
1689 atbb:
1690 image: ghcr.io/malpercio-dev/atbb:v1.0.0
1691 container_name: atbb
1692 restart: unless-stopped
1693 ports:
1694 - "127.0.0.1:8080:80" # Bind to localhost only
1695 env_file:
1696 - .env.production
1697 healthcheck:
1698 test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/api/healthz"]
1699 interval: 30s
1700 timeout: 3s
1701 retries: 3
1702```
1703
1704---
1705
1706## Appendix: Quick Reference
1707
1708### Required Environment Variables
1709
1710```bash
1711DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require
1712FORUM_DID=did:plc:xxxxxxxxxxxxx
1713PDS_URL=https://bsky.social
1714FORUM_HANDLE=forum.example.com
1715FORUM_PASSWORD=strong_password_16+_chars
1716OAUTH_PUBLIC_URL=https://forum.example.com
1717SESSION_SECRET=64_hex_chars_from_openssl_rand
1718```
1719
1720### Essential Commands
1721
1722```bash
1723# Pull image
1724docker pull ghcr.io/malpercio-dev/atbb:latest
1725
1726# Run migrations
1727docker run --rm --env-file .env.production \
1728 ghcr.io/malpercio-dev/atbb:latest \
1729 pnpm --filter @atbb/appview db:migrate
1730
1731# Start container
1732docker run -d --name atbb --restart unless-stopped \
1733 -p 8080:80 --env-file .env.production \
1734 ghcr.io/malpercio-dev/atbb:latest
1735
1736# View logs
1737docker logs -f atbb
1738
1739# Stop/restart
1740docker stop atbb
1741docker restart atbb
1742
1743# Health check
1744curl http://localhost:8080/api/healthz
1745```
1746
1747### Support Resources
1748
1749- **Documentation:** https://github.com/malpercio-dev/atbb-monorepo/tree/main/docs
1750- **Issues:** https://github.com/malpercio-dev/atbb-monorepo/issues
1751- **Releases:** https://github.com/malpercio-dev/atbb-monorepo/releases
1752- **AT Protocol Docs:** https://atproto.com/docs
1753
1754### Security Checklist
1755
1756Before going to production:
1757
1758- [ ] Generated `SESSION_SECRET` with `openssl rand -hex 32`
1759- [ ] Used strong, unique passwords (minimum 16 characters)
1760- [ ] Enabled database SSL/TLS (`?sslmode=require`)
1761- [ ] Set `OAUTH_PUBLIC_URL` to HTTPS domain (not HTTP)
1762- [ ] Set file permissions: `chmod 600 .env.production`
1763- [ ] Never committed `.env.production` to version control
1764- [ ] Configured reverse proxy with HTTPS (Caddy/nginx)
1765- [ ] Set up database backups
1766- [ ] Configured log rotation
1767- [ ] Set up health monitoring
1768- [ ] Restricted firewall to ports 80/443 only
1769- [ ] Tested backup restoration procedure
1770
1771---
1772
1773**End of Deployment Guide**
1774
1775For questions or issues not covered here, please open an issue at:
1776https://github.com/malpercio-dev/atbb-monorepo/issues