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