A Discord bot needs to run 24/7 — your laptop is not a hosting strategy. This guide takes a [discord.js](https://discord.js.org) bot to production hosting, covering the architectural choice that trips people up (gateway vs HTTP interactions) and how to keep it always-on with real persistence.
Gateway bot vs HTTP interactions
There are two ways a Discord bot receives events:
| Model | How | Hosting shape |
|---|---|---|
| Gateway (WebSocket) | Bot holds a persistent connection to Discord | long-running worker |
| HTTP interactions | Discord POSTs to your endpoint | web service / edge function |
Most discord.js bots use the gateway — a persistent WebSocket connection that receives messages, reactions, voice events, and more in real time. This means your bot is a long-running background process, not a web server. It doesn't listen on a port for the public; it dials out to Discord and stays connected. Deploy it as a worker-style service.
HTTP interactions (slash commands only, no gateway) suit serverless/edge functions because they're request-response. If your bot only needs slash commands, the edge-function path is cheaper and scales to zero — but you lose real-time gateway events.
A minimal gateway bot
import { Client, GatewayIntentBits } from 'discord.js'
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
})
client.once('ready', () => console.log(`Logged in as ${client.user.tag}`))
client.on('interactionCreate', async (i) => {
if (i.isChatInputCommand() && i.commandName === 'ping') {
await i.reply('Pong!')
}
})
client.login(process.env.DISCORD_TOKEN)The token comes from the [Discord Developer Portal](https://discord.com/developers/applications). Never commit it — inject DISCORD_TOKEN as a secret. If it leaks, regenerate it immediately; a leaked token is full control of your bot.
Intents matter
Discord gates certain events behind privileged intents (e.g. message content, guild members). Enable them both in the Developer Portal and in your intents array, or your handlers silently never fire. The MessageContent intent in particular is required to read message text and is a common cause of "my bot doesn't respond."
Registering slash commands
Slash commands must be registered with Discord's API, separately from running the bot. Do this with a one-off script (or a cronjob/deploy step), not on every startup:
import { REST, Routes } from 'discord.js'
const rest = new REST().setToken(process.env.DISCORD_TOKEN)
await rest.put(
Routes.applicationCommands(process.env.CLIENT_ID),
{ body: commands },
)Global commands can take up to an hour to propagate; guild-scoped commands are instant — use guild scope while developing.
Deploying as a worker
Because it's a gateway bot, deploy it as a background worker service:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "src/index.js"]On PandaStack, deploy from your Git repo as a container service with no public port and inject DISCORD_TOKEN. Important: a gateway bot must not scale to zero — it needs to stay connected to receive events. Keep it on an always-on tier rather than a scale-to-zero free-tier app, otherwise it disconnects when idle and misses events.
Persistence
Most bots need state — per-guild settings, user XP, reminders. Don't store it in memory; a restart wipes it. Attach a managed PostgreSQL and use the auto-wired DATABASE_URL, or managed Redis for fast ephemeral state. A reminders feature, for example, can store due times in Postgres and use a cronjob to fire them.
Go-live checklist
- Deployed as a long-running worker (gateway), not scale-to-zero
DISCORD_TOKENas a secret; never committed- Required intents enabled in portal + code
- Slash commands registered as a separate step
- Persistent storage for bot state
- Auto-reconnect handled (discord.js does this; just don't kill the process)
References
- [discord.js guide](https://discordjs.guide/)
- [Discord Developer Portal](https://discord.com/developers/docs/intro)
- [Discord gateway intents](https://discord.com/developers/docs/topics/gateway#gateway-intents)
- [Registering slash commands](https://discordjs.guide/creating-your-bot/command-deployment.html)
A discord.js gateway bot runs as an always-on PandaStack worker service with a managed database for state and cronjobs for scheduled actions. Deploy your bot on the free tier at [dashboard.pandastack.io](https://dashboard.pandastack.io).