Docker & DevOps

Run a Discord Bot 24/7 on a VPS with Docker

Keep your Discord bot online around the clock by packaging it in a Docker image, passing the token as an environment variable and letting a restart policy bring it back after crashes or reboots.

BeginnerLinux server5 min read

To run a Discord bot 24/7 on a VPS, you package the bot in a Docker image, pass the token as an environment variable and set a restart policy so Docker brings it back after a crash or a reboot. The steps below cover the Dockerfile, the env file, the run command and how to read the logs when something goes wrong.

Before you start

You need:

  • A Linux VPS with SSH access. Your IP, username and password are in your VPS provider's control panel.
  • Docker installed. If it is not there yet, follow Install Docker and Docker Compose on a Linux VPS.
  • A Discord application and bot user created in the Discord Developer Portal, with the bot token copied somewhere safe.
  • Either your own bot code or the small example below.
  • About 15 minutes.

Note: A bot that serves a few servers runs fine on a small VPS. A Python or Node bot usually idles well under 100 MB of RAM, so memory is rarely the limit — long log files are a more common problem.

Prepare the bot project

Create a working directory and move into it:

Bash
mkdir -p /opt/discord-bot
cd /opt/discord-bot

Create requirements.txt with the library:

discord.py

Then create bot.py with a minimal bot that replies to a ping:

Python
import os
import discord

TOKEN = os.environ["DISCORD_TOKEN"]

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)


@client.event
async def on_ready():
    print(f"Logged in as {client.user}", flush=True)


@client.event
async def on_message(message):
    if message.author == client.user:
        return
    if message.content == "!ping":
        await message.channel.send("pong")


client.run(TOKEN)

The token is read from the environment with os.environ, never hard-coded.

Tip: Once the bot works, pin the library version in requirements.txt so a rebuild months later does not pull a breaking change. Check the library's own docs for the version you should pin.

Write the Dockerfile

Create /opt/discord-bot/Dockerfile:

Dockerfile
FROM python:3-slim

ENV PYTHONUNBUFFERED=1
WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY bot.py ./

RUN useradd --create-home --shell /usr/sbin/nologin bot
USER bot

CMD ["python", "bot.py"]

Three things matter here. PYTHONUNBUFFERED=1 makes print output appear in docker logs immediately instead of sitting in a buffer. Copying requirements.txt before the code keeps the dependency layer cached, so code edits rebuild in seconds. And the USER bot line means the process does not run as root inside the container.

Build the image:

Bash
docker build -t my-discord-bot:1.0 .

Expect a few lines of pip output and a final line naming the image my-discord-bot:1.0.

Pass the token as an environment variable

Warning: Anyone who has your bot token controls your bot. Never put it in the Dockerfile, in bot.py, in a Git repository or on the command line — command-line values end up in the shell history.

Create the env file and lock it down:

Bash
nano /opt/discord-bot/bot.env
chmod 600 /opt/discord-bot/bot.env

Put one line inside it, with no quotes and no spaces around the =:

INI
DISCORD_TOKEN=PASTE_YOUR_TOKEN_HERE

Docker reads this file at start-up and injects DISCORD_TOKEN as an environment variable inside the container. The file stays on the host with mode 600, so only root can read it.

Run a Discord bot 24/7 on a VPS with Docker

Start the container:

Bash
docker run -d \
  --name discord-bot \
  --restart unless-stopped \
  --env-file /opt/discord-bot/bot.env \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  my-discord-bot:1.0

What each flag does:

  • -d runs the container in the background and prints its ID.
  • --name discord-bot gives it a stable name for docker logs, docker restart and docker stop.
  • --restart unless-stopped restarts the container after a crash and after a VPS reboot, unless you stopped it yourself on purpose.
  • --env-file loads the token from the file you just created.
  • --log-opt max-size and --log-opt max-file cap the log at 10 MB per file and keep three files, so a noisy bot cannot fill the disk.

No port mapping is needed. A Discord bot opens outbound connections, so there is nothing to expose or open in a firewall. If you want the full list of flags, see Docker run flags you will actually use.

Check the logs

Confirm the container is running:

Bash
docker ps --filter name=discord-bot

Read the last 50 lines:

Bash
docker logs --tail 50 discord-bot

Follow the log live until you press Ctrl+C:

Bash
docker logs -f discord-bot

A healthy start looks like Logged in as YourBot#1234, followed by the bot joining its servers.

Check it works

  1. docker ps lists discord-bot with status Up.
  2. docker logs discord-bot shows the login line.
  3. In a channel the bot can read, type !ping and expect pong.
  4. Test the restart policy: run docker kill discord-bot, wait a few seconds, then run docker ps again. The container should be back up on its own.
  5. Test a reboot: run sudo reboot, reconnect over SSH once the server is back and check docker ps again. The container starts automatically because of the restart policy.

Troubleshooting

  • Log says the token is invalid and the container exits — the token was copied wrong or has been regenerated in the Developer Portal. Copy the current token into bot.env, then recreate the container: docker rm -f discord-bot and run the docker run command again.
  • The bot appears online but never answers — the message content intent is not enabled for your application in the Developer Portal. Enable it there, or switch the bot to slash commands, which do not need that intent.
  • The container restarts in a loop — a crash on start-up, usually a missing dependency or a syntax error. docker logs --tail 100 discord-bot shows the traceback. Add the missing package to requirements.txt, rebuild and recreate the container.
  • Edits to bot.py have no effect — you changed the file but not the image. Rebuild with a new tag, for example docker build -t my-discord-bot:1.1., then recreate the container. See Roll back a bad deploy using Docker image tags.
  • The disk fills up — logs are not capped. Recreate the container with the --log-opt max-size=10m and --log-opt max-file=3 flags shown above.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.