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