Deployment
Production deployment with Docker, Dokploy, and environment variables.
Self-hosting preview — Managed AttendiBot hosting is the recommended path today. Self-hosting support is coming soon; this guide is published for early adopters and operators preparing their infrastructure.
Production deployment guide for AttendiBot. The system consists of three components: Rust backend (bot + API), PostgreSQL, and Next.js web (BFF + dashboard).
Architecture overview
Key principle: The Rust API on port 8080 should be reachable from the web BFF only — not exposed publicly. Auth.js routes (/api/auth/*) must stay on the Next.js app.
Prerequisites
- Docker (recommended) or bare-metal with Rust + Node.js
- PostgreSQL 18+
- Discord application configured — see Discord Setup
- Domain with HTTPS for the web app
Backend deployment
Docker (recommended)
Build and run from backend/:
cd backend
docker compose up --buildThe Dockerfile uses cargo-chef with BuildKit cache mounts for faster rebuilds.
Production environment variables
| Variable | Required | Description |
|---|---|---|
DISCORD_TOKEN | Yes | Discord bot token |
ENVIRONMENT | Yes | Set to production |
DATABASE_URL | Yes | PostgreSQL connection string |
WEBHOOK_SECRET | Yes | Must not be default placeholder |
API_INTERNAL_SECRET | Yes | Must not be default placeholder |
HTTP_PORT | No | Default 8080 |
RUST_LOG | No | e.g. info,attendibot_bot=debug |
BOT_ACTIVITY_TEXT | No | Bot presence string |
Production rejects default placeholders for WEBHOOK_SECRET and API_INTERNAL_SECRET.
Dokploy notes
- Keep Clean Cache disabled for normal deploys (preserves Docker layer and BuildKit caches)
- Enable Clean Cache only when debugging stale build artifacts
- Typical build times: 1–2 min (source change), 3–4 min (cold or dependency change)
Health check
curl http://localhost:8080/healthExpected: {"status":"ok"} (when database is connected)
Web deployment
Docker / Dokploy
Build from the attendibot-web repo root (Fumadocs MDX in content/docs/ is compiled at build time):
docker build -t attendibot-web .When working from the AttendiBot monorepo checkout, regenerate MDX from markdown before building:
cd web && npm run sync-docsDokploy should use the web repository with build context . (repo root) and Dockerfile at the repository root.
Runtime environment variables:
| Variable | Description |
|---|---|
AUTH_SECRET | Auth.js encryption secret |
AUTH_DISCORD_ID | Discord OAuth client ID |
AUTH_DISCORD_SECRET | Discord OAuth client secret |
ATTENDIBOT_API_BASE_URL | Internal backend URL (see below) |
ATTENDIBOT_API_INTERNAL_SECRET | Must match backend API_INTERNAL_SECRET |
NEXT_PUBLIC_SITE_URL | Public site URL (HTTPS) |
STRIPE_SECRET_KEY | Stripe secret key for Checkout and webhooks |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret |
STRIPE_PRICE_PRO_MONTHLY | Stripe Price ID for Pro monthly |
STRIPE_PRICE_PRO_ANNUAL | Stripe Price ID for Pro annual |
STRIPE_PRICE_ENTERPRISE_MONTHLY | Stripe Price ID for Enterprise monthly |
STRIPE_PRICE_ENTERPRISE_ANNUAL | Stripe Price ID for Enterprise annual |
See billing setup for Stripe Dashboard configuration.
Build args (Docker):
| Variable | Description |
|---|---|
NEXT_PUBLIC_DISCORD_INVITE_URL | Full bot invite URL |
NEXT_PUBLIC_DISCORD_CLIENT_ID | Discord app client ID |
Web → backend connectivity
Set on the web service:
ATTENDIBOT_API_BASE_URL=http://attendibot-backend-xxx:8080
ATTENDIBOT_API_INTERNAL_SECRET=<same as backend>Use the backend container's internal Docker hostname, not the public site URL.
Verify from the web container:
node -e "console.log(process.env.ATTENDIBOT_API_BASE_URL)"
node -e "fetch(process.env.ATTENDIBOT_API_BASE_URL+'/health').then(r=>r.text()).then(console.log)"Vercel
Deploy web/ to Vercel. Set the same environment variables. Point ATTENDIBOT_API_BASE_URL to your backend's reachable URL (ensure network access from Vercel to your backend).
Invite URL after deploy
For "Add to Discord" to work, set at least one of:
- Runtime:
AUTH_DISCORD_IDorDISCORD_INVITE_URL - Build time:
NEXT_PUBLIC_DISCORD_INVITE_URLorNEXT_PUBLIC_DISCORD_CLIENT_ID
Verify: view page source, search for oauth2/authorize, confirm client_id= is present.
Database
PostgreSQL stores all session records, leaderboard data, signing keys, and configuration.
Migrations
Migrations run automatically when the backend starts (sqlx::migrate!).
Migration files in backend/migrations/:
| Migration | Purpose |
|---|---|
| 001 | Initial schema |
| 002 | Voice tracking tables |
| 003 | Log destinations |
| 004 | Drop legacy guild configs |
| 005 | Reset anchor modes |
| 006 | Competitive features (fairness, subscriptions, webhooks, verified roles) |
Backups
Back up PostgreSQL regularly. Signing keys (encrypted) and all session records live in the database.
Post-deploy checklist
After deployment:
- Backend health check passes (
/health) - Web can reach backend (verify from web container)
- Discord OAuth redirect URI matches production URL
- "Add to Discord" button works
- Sign in to dashboard succeeds
- Generate and unlock signing keys
- Configure tracking and test a voice session
- Verify a session on the public verifier
See also docs/marketing/post-deploy-checklist.md for SEO and bot directory submission.
Network security
| Rule | Reason |
|---|---|
Do not expose /api/v1 publicly | Requires BFF JWT; bypass breaks auth model |
Keep /api/auth/* on Next.js | Auth.js handles OAuth callbacks |
Use strong secrets for API_INTERNAL_SECRET and WEBHOOK_SECRET | Prevents unauthorized API access |
| HTTPS on web app | Required for Discord OAuth |