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