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