# atBB Deployment Infrastructure Design **Date:** 2026-02-11 **Status:** Approved for Implementation ## Overview This document outlines the deployment infrastructure for atBB: Docker containerization, GitHub Actions CI/CD pipeline, and administrator deployment guide. ## Goals 1. Package both services in one Docker image for simple deployment 2. Automate image builds and publishing to GitHub Container Registry (GHCR) 3. Document the complete deployment process 4. Support single-instance and production deployments ## Non-Goals - Redis session storage integration (future work) - Multi-instance/load-balanced deployments (requires Redis first) - Kubernetes-specific manifests (docker-compose example sufficient for now) ## Architecture Decisions ### Container Architecture **Decision:** Single Docker container with both appview and web services, using nginx for internal routing and a process manager to run both apps. **Rationale:** - Operators deploy one artifact - Preserves app separation without refactoring - Simple to understand and debug - Scales to medium deployments **Structure:** ``` ┌─────────────────────────────────────┐ │ atBB Container (Port 80) │ │ ┌──────────────────────────────┐ │ │ │ Nginx (port 80) │ │ │ │ Routes: │ │ │ │ - /api/* → appview:3000 │ │ │ │ - /* → web:3001 │ │ │ └──────────────────────────────┘ │ │ │ │ ┌────────────┐ ┌──────────────┐ │ │ │ appview │ │ web │ │ │ │ (port 3000) │ (port 3001) │ │ │ └────────────┘ └──────────────┘ │ │ │ │ Process Manager: Simple shell │ │ script or npm-run-all │ └─────────────────────────────────────┘ ``` **Alternatives Considered:** - Separate containers for appview and web: More complex, overkill for current scale - Merge apps into single Hono service: Would require refactoring, couples the apps ### Reverse Proxy **Decision:** Nginx inside the container for internal routing. Recommend Caddy outside the container for operators' infrastructure. **Rationale:** - Nginx inside: Standard, well-known, simple configuration for routing between two apps - Caddy outside: Modern, easy automatic HTTPS for operators - Clear separation: internal routing vs. external TLS/domain management ### Database Strategy **Decision:** External PostgreSQL database. Provide docker-compose example for testing. **Rationale:** - Production best practice: managed database services (AWS RDS, DigitalOcean, etc.) - Easier backups, scaling, monitoring - Docker compose example lowers barrier for testing/development **Alternatives Considered:** - Include postgres in same container: Bad practice, complicates backups/scaling - Require docker-compose only: Too rigid, doesn't support managed DB deployments ### Session Storage **Decision:** In-memory sessions only (current implementation). Document Redis as future enhancement. **Rationale:** - Application lacks Redis integration - In-memory storage suffices for single-instance deployments - Admin guide documents limitations and future plans **Implications:** - Sessions lost on container restart - No multi-instance deployment support yet - Operators must be aware of this limitation ### Port Exposure **Decision:** Expose only port 80 from the container. **Rationale:** - Clean interface: one port for the entire forum - Operators map their host port to container's 80 (e.g., `-p 8080:80`) - If debugging needed, operators can `docker exec` into container **Alternatives Considered:** - Also expose 3000 and 3001: Adds complexity, rarely needed ### Build Strategy **Decision:** Multi-stage Docker build with separate build and runtime stages. **Stage 1 - Builder:** - Base: `node:22-alpine` - Install pnpm globally - Copy entire monorepo (respecting .dockerignore) - `pnpm install --frozen-lockfile` (all dependencies including dev) - `pnpm build` (turbo builds lexicon → appview + web) **Stage 2 - Runtime:** - Base: `node:22-alpine` - Install nginx and process management tools - Copy only production files from builder: - Workspace configs, package.json files - `apps/appview/dist/`, `apps/web/dist/` - `packages/*/dist/` (db, lexicon) - `pnpm install --prod --frozen-lockfile` (production deps only) - Copy nginx config and entrypoint script **Benefits:** - Small final image (~200-250MB vs ~1GB single-stage) - Faster deploys, less attack surface - Production image contains no build tools or dev dependencies **Alternatives Considered:** - Single-stage build: Simple but results in huge images with unnecessary tools - Use Nix/devenv in container: Matches local dev but significantly larger, unnecessary complexity ### CI/CD Pipeline **Decision:** Two GitHub Actions workflows **Workflow 1: Pull Request Checks** (`.github/workflows/ci.yml`) - Triggers: On PR open/update - Jobs (parallel after setup): - Lint: `pnpm turbo lint` - Test: `pnpm test` - Build: `pnpm build` (verify compilation) - Blocks PR merge if any job fails - Does NOT build Docker image (too expensive for every PR) **Workflow 2: Build and Publish** (`.github/workflows/publish.yml`) - Triggers: - Push to `main` branch (after PR merge) - Tag push matching `v*` (e.g., `v1.0.0`) - Prerequisites: Reuses CI checks, only builds if they pass - Jobs: - Build Docker image (multi-stage) - Push to GHCR: `ghcr.io//atbb` - Image tags: - On main push: `latest`, `main-` - On version tag: ``, `latest` **Rationale:** - Fast PR feedback without slow Docker builds - Builds only validated code - Supports bleeding-edge (latest) and versioned releases - Operators can deploy specific SHAs ### Environment Configuration **Decision:** Document both `.env` file and individual `-e` flags. **Primary method:** Environment file ```bash docker run --env-file .env ghcr.io//atbb:latest ``` **Alternative:** Individual flags (for orchestrators) ```bash docker run -e DATABASE_URL=... -e FORUM_DID=... ghcr.io//atbb:latest ``` **Rationale:** - Env file is easier for simple deployments - Individual flags support Kubernetes/orchestration tools - Flexibility for different deployment scenarios ### Database Migrations **Decision:** Manual migration step before starting container. **Process:** ```bash # Run migrations using one-off container docker run --env-file .env ghcr.io//atbb:latest \ pnpm --filter @atbb/appview db:migrate # Then start the main container docker run -p 80:80 --env-file .env ghcr.io//atbb:latest ``` **Rationale:** - Database migrations are sensitive operations that should be deliberate - Explicit control over when migrations happen - Prevents race conditions with multiple container instances - Clear failure mode: migration fails → container doesn't start **Alternatives Considered:** - Auto-run on startup: Convenient but risky, concurrent instances conflict - Separate migration image: More complexity, two images to maintain ## Implementation Components ### 1. Dockerfile Multi-stage build as described above. Key files: - `/Dockerfile` (at monorepo root) - `/nginx.conf` (nginx routing configuration) - `/entrypoint.sh` (starts nginx + both apps) - `/.dockerignore` (exclude node_modules, .git, .env, tests) ### 2. GitHub Actions Workflows **`.github/workflows/ci.yml`:** - Setup job: checkout, Node.js, pnpm, install deps - Parallel jobs: lint, test, build - Matrix strategy for Node.js versions? (optional) **`.github/workflows/publish.yml`:** - Trigger conditions: main push, tag push - Use Docker buildx for multi-arch builds (optional) - Login to GHCR with `GITHUB_TOKEN` - Build and push with appropriate tags - Output image SHA for traceability ### 3. Administrator's Guide **Document:** `docs/deployment-guide.md` **Sections:** 1. **Prerequisites** - PostgreSQL 14+ (managed service recommended) - Domain name + DNS - Container runtime - **AT Protocol Setup:** - Forum account DID (forum identity) - PDS instance to host forum records - Forum credentials (FORUM_HANDLE, FORUM_PASSWORD) - Understanding of lexicon namespace (`space.atbb.*`) 2. **Quick Start** - Pull image from GHCR - Create `.env.production` from template - Run migrations (one-time) - Start container - Configure reverse proxy (Caddy example) 3. **Environment Configuration** - Required: `DATABASE_URL`, `FORUM_DID`, `PDS_URL`, `OAUTH_PUBLIC_URL`, `SESSION_SECRET` - Optional: `JETSTREAM_URL`, `SESSION_TTL_DAYS`, `PORT` overrides - How to generate `SESSION_SECRET`: `openssl rand -hex 32` - Future: `REDIS_URL` (not yet implemented) 4. **Database Setup** - PostgreSQL provisioning options - Running migrations command - Migration troubleshooting 5. **Running the Container** - Basic: `docker run -p 80:80 --env-file .env ghcr.io//atbb:latest` - With version: `ghcr.io//atbb:v1.0.0` - Health checks: `/api/health` 6. **Reverse Proxy Setup** - **Caddy (Recommended):** ``` your-forum.com { reverse_proxy localhost:80 } ``` - Automatic HTTPS via Let's Encrypt - Alternatives: nginx, Traefik examples 7. **Monitoring & Logs** - Container logs: `docker logs ` - Log format: JSON structured logs - Health endpoint - Future: Metrics/observability 8. **Upgrading** - Pull new image - Check release notes for migration requirements - Run migrations if needed - Stop old container, start new - Downtime note: sessions will reset (in-memory storage) 9. **Troubleshooting** - Database connection issues - PDS connectivity problems - OAuth misconfiguration - Debug mode via environment variable 10. **Docker Compose Example** - Full `docker-compose.example.yml` with: - PostgreSQL service - atBB service - Volume mounts for persistence - Network configuration - Future: Redis service (commented out) ### 4. Supporting Files **`.env.production.example`:** - Template for production environment variables - Comments explaining each variable - Security notes (SESSION_SECRET generation, etc.) **`docker-compose.example.yml`:** - Complete working example for testing - PostgreSQL with volume persistence - atBB service with proper depends_on - Health checks configured ## Testing Strategy ### Manual Testing Checklist Before merging: 1. Build Dockerfile locally: `docker build -t atbb:test .` 2. Verify image size is reasonable (~200-250MB) 3. Run container with test database: `docker run -p 8080:80 --env-file .env.test atbb:test` 4. Verify nginx routes work: - `curl http://localhost:8080/api/health` → appview - `curl http://localhost:8080/` → web 5. Test migration command works 6. Test docker-compose example ### CI/CD Testing - PR workflow runs on every push to PR - Verify it fails when tests fail - Verify publish workflow only runs after merge - Check GHCR for published images with correct tags ## Security Considerations 1. **Secrets Management:** - Never commit `.env` files - Document `SESSION_SECRET` generation - Warn operators to secure env files 2. **Image Security:** - Use official Node.js alpine images - No unnecessary tools in runtime image - Regular dependency updates via Dependabot 3. **Network Security:** - Container only exposes port 80 - Recommend TLS termination at reverse proxy - Document CSP/security headers for Caddy/nginx 4. **Database Security:** - Document least-privilege PostgreSQL user setup - SSL/TLS for database connections - Regular backup procedures ## Open Questions - [ ] Organization name for GHCR path: `ghcr.io//atbb` - what should `` be? - [ ] Do we want multi-arch builds (amd64 + arm64)? - [ ] Should we add healthcheck to Dockerfile itself? - [ ] Node.js version matrix in CI (test multiple versions)? ## Future Enhancements 1. **Redis Integration:** - Implement Redis session storage in application - Update Dockerfile to support Redis connection - Document multi-instance deployment - Update docker-compose with Redis service 2. **Observability:** - Prometheus metrics endpoint - Structured logging with levels - OpenTelemetry tracing 3. **Kubernetes:** - Example k8s manifests - Helm chart - StatefulSet for potential future needs 4. **Performance:** - Static asset caching in nginx - Gzip compression - HTTP/2 support ## Success Criteria - [ ] Dockerfile builds successfully and produces ~200-250MB image - [ ] Both PR checks and publish workflows pass in GitHub Actions - [ ] Image is published to GHCR with correct tags - [ ] Administrator's guide is clear and complete - [ ] docker-compose.example.yml works out of the box - [ ] Manual testing checklist passes - [ ] Operators can deploy with just image + env file + migrations