Docs academy · Guide 6 of 8 · 12 min read

Hosting 24/7: VPS, Docker, process managers

Get your bot off your laptop and keep it running through crashes and reboots.


Your bot only runs while its program runs. To stay online all the time it needs a computer that's always on, which usually means a small rented server.

Choosing a host

| Option | Good for | Watch out for | | --- | --- | --- | | VPS (a small Linux server you rent) | Most bots. You get full control at a low monthly cost. | You're responsible for updates and security. | | Bot or app hosting platforms | Getting started without learning Linux | Check how they treat long-running processes, and watch usage limits. | | Your own always-on machine | Testing | Home internet and power cuts mean downtime. |

A small bot runs happily on a VPS with 1 vCPU and 1 GB of memory. This guide uses a VPS running Ubuntu.

Prepare the server

Log in over SSH, then install updates and create a normal user, because your bot should never run as root:

sudo apt update && sudo apt upgrade -y
sudo adduser --disabled-password --gecos "" bot

Turn on the firewall. A Discord bot only makes outgoing connections, so nothing needs to come in except SSH:

sudo ufw allow OpenSSH
sudo ufw enable

Log in with SSH keys rather than a password if you can. Password logins are attacked constantly.

Get your code onto the server with git clone. Create your .env by hand on the server, since it isn't in your repository (and must never be). Protect it so that only your bot's user can read it:

chmod 600 .env

Option 1: systemd

Ubuntu already includes systemd, and it restarts your bot after a crash or a reboot. It's the option we recommend for a single bot.

Create /etc/systemd/system/mybot.service. For a Node.js bot:

[Unit]
Description=My Discord bot
After=network-online.target
Wants=network-online.target

[Service]
User=bot
WorkingDirectory=/home/bot/my-bot
EnvironmentFile=/home/bot/my-bot/.env
ExecStart=/usr/bin/node index.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

For Python, change ExecStart to use your virtual environment:

ExecStart=/home/bot/my-bot/.venv/bin/python bot.py

Then start it, and have it start at boot:

sudo systemctl daemon-reload
sudo systemctl enable --now mybot

These are the commands you'll use day to day:

systemctl status mybot
journalctl -u mybot -f
sudo systemctl restart mybot

Option 2: PM2

PM2 is a popular process manager for Node.js, if you'd rather not write a service file.

npm install -g pm2
pm2 start index.js --name my-bot --node-args="--env-file=.env"
pm2 save
pm2 startup

pm2 startup prints one command. Run it to have your bot start after a reboot. Then use pm2 logs my-bot and pm2 restart my-bot.

Option 3: Docker

Docker packages your bot with everything it needs, so it runs the same way everywhere. It's worth it once you run several services together, such as a bot and a database.

A Dockerfile for a Node.js bot:

FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "index.js"]

For Python:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nobody
CMD ["python", "bot.py"]

Add a .dockerignore containing .env, node_modules and .venv, so that secrets don't get baked into the image.

A compose.yaml to run it, with restarts:

services:
  bot:
    build: .
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./data:/app/data

Anything inside the container disappears when it's rebuilt, so keep a SQLite file in the mounted data folder and point your bot at data/bot.db.

docker compose up -d --build
docker compose logs -f bot

Handling shutdowns

When you restart, your process gets a signal asking it to stop. Close things cleanly so that you don't lose data:

process.on("SIGTERM", async () => {
  await client.destroy();
  db.close();
  process.exit(0);
});

In discord.py, bot.run handles the Discord connection for you. Close your own resources by overriding close on your client:

async def close(self):
    await self.db.close()
    await super().close()

Updating your bot

A simple routine that works:

git pull
npm ci
sudo systemctl restart mybot
journalctl -u mybot -n 50

Always read the logs after restarting, to confirm that it came back up.

Know when it's down

Hosting isn't finished until you find out about problems before your users do:

  • Log errors somewhere you'll see them. A private Discord channel through a webhook is an easy start.
  • Post stats to Nexobots regularly (see guide 8). It keeps your listing fresh, and it's one of the ways we know your bot is alive. Listings that look offline for 7 days get a warning, and they're archived after 30.

Next

Your bot runs around the clock. Now let it grow, with Sharding and getting verified.

Still stuck? Open a ticket and a mentor will take a look. We'll know which guide you were on.