Self-hosting
Run the bot, the website and the database yourself, for development or for your own community.
You do not need this page to use Roleplay+ Tickets. It is for operators who run their own instance, and for development.
#Requirements
- Node.js 20.11 or newer
- Docker for PostgreSQL, or PostgreSQL 15 or newer
- A Discord application with a bot user
#Repository layout
| Folder | Content |
|---|---|
apps/bot | The discord.js bot: panels, tickets, transcripts, automations, commands and an internal HTTP API |
apps/web | Next.js website, docs, dashboard, Discord sign-in, billing and the transcript viewer |
packages/shared | Config schemas with defaults, the variable engine, plans and the API contract |
packages/db | Database schema, migrations and a demo seed |
The bot owns the connection to Discord. The website reads and writes settings in the database and asks the bot, through its internal API, for live server data and to send panels.
#Discord application
- Create an application in the Discord developer portal (opens in a new tab).
- Under Bot, create the bot and copy its token. Turn on the privileged intents Server Members and Message Content. Prefix and custom commands, automations, transcripts and the owner left check need them.
- Under OAuth2, copy the client id and the client secret. Add the redirect
${PUBLIC_URL}/api/auth/callback, for local developmenthttp://localhost:3000/api/auth/callback.
#Configuration
Copy .env.example to .env in the repository root. Both apps read it.
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_TOKEN=
# Optional: register slash commands on one server only, instantly
DISCORD_DEV_GUILD_ID=
DATABASE_URL=postgres://tickets:tickets@localhost:5433/tickets
PUBLIC_URL=http://localhost:3000
# 32 or more random characters: openssl rand -hex 32
SESSION_SECRET=
# Discord user ids with access to the platform admin page
PLATFORM_ADMINS=
BOT_API_URL=http://localhost:4100
BOT_API_PORT=4100
BOT_API_SECRET=
# Encrypts whitelabel bot tokens; bot and web need the same value
ENCRYPTION_KEY=
# Optional billing
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRICE_MONTHLY=
STRIPE_PRICE_YEARLY=
STRIPE_PRICE_WHITELABEL_MONTHLY=
STRIPE_PRICE_WHITELABEL_YEARLY=
# Optional website extras
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=
NEXT_PUBLIC_SUPPORT_URL=SESSION_SECRET encrypts the Discord tokens of signed-in users and signs their sessions. BOT_API_SECRET protects the internal API between website and bot. Use long random values for both and never commit them.
ENCRYPTION_KEY encrypts the tokens of whitelabel bots. Without it, SESSION_SECRET is used. Bot and website need the same value, and changing it later makes stored tokens unreadable, so customers would have to paste them again.
#Local development
cp .env.example .env
docker compose up -d db # PostgreSQL on localhost:5433
npm install
npm run db:migrate
npm run commands:deploy # registers the slash commands
npm run dev # bot and website, website on port 3000Without DISCORD_DEV_GUILD_ID, Discord can take up to an hour to show new slash commands everywhere.
To look at the dashboard without a Discord application, set DEV_LOGIN=1, run npm run seed and open http://localhost:3000/api/auth/dev. This sign-in does not work in production.
#Production
docker compose --profile prod up -d --buildThis builds the images, runs the migrations once and starts the bot and the website on port 3000. Put a reverse proxy with TLS in front of the website and set PUBLIC_URL to its public address. Only the website must be reachable from the internet. The bot API stays inside the Docker network.
Uploaded panel images are served at ${PUBLIC_URL}/media/… and Discord loads them from there. They only appear in Discord when PUBLIC_URL is a public https address; on a local setup the bot leaves them out, image links still work.
#Billing with Stripe
Billing is optional. Without it, the checkout shows Coming soon and premium keys from the admin page still work.
- Create a product with a monthly and a yearly price in Stripe. Put the price ids into
STRIPE_PRICE_MONTHLYandSTRIPE_PRICE_YEARLY, the secret key intoSTRIPE_SECRET_KEY. - Add a webhook endpoint
${PUBLIC_URL}/api/billing/webhookwith the eventscheckout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deletedandinvoice.paid. - Put the signing secret of the webhook into
STRIPE_WEBHOOK_SECRET. - For the Whitelabel add-on, create a second product with a monthly and a yearly price and put the price ids into
STRIPE_PRICE_WHITELABEL_MONTHLYandSTRIPE_PRICE_WHITELABEL_YEARLY.
#Scaling
The web app can run as many replicas as you like. The bot scales in clusters: every bot container runs some shards of the main bot and some whitelabel bots, and the web app sends each request to the container that serves the server.
- Set
BOT_CLUSTER_COUNTto the number of bot containers, for example3. - Set
DISCORD_SHARD_COUNTto a fixed number of shards, a multiple of the cluster count and at least one per 2,500 servers, for example6. - Run the bot service with that many replicas and the same environment. Each container claims a free cluster slot by itself.
- Give the web app the same two values and redeploy both.
The platform admin shows every cluster under Bots. Dokploy setup, rolling deploys and the details are in docs/deployment.md in the repository.
If you run a public instance, you are the operator. Fill in the imprint, privacy policy and terms templates with your details before you open it to others.