Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Bot Discorda 24/7 na VPS: systemd lub Docker, token i logi

Bot Discorda w Pythonie (discord.py) albo Node.js (discord.js) działający całą dobę: token w pliku środowiskowym, systemd lub Docker, logi i restart po awarii.

Aplikacje na własnym serwerzezaktualizowano 6 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

Bot Discorda, który działa całą dobę: startuje sam po restarcie serwera, wstaje po awarii, zapisuje logi w jednym miejscu, a jego token nie ląduje ani w repozytorium, ani w historii poleceń. Pokazujemy dwie drogi – bota w Pythonie (discord.py) jako usługę systemd i bota w Node.js (discord.js) w Dockerze. Wybierz tę, która pasuje do języka, w którym piszesz; obie działają na Ubuntu 24.04 LTS oraz Debianie 12 i 13.

Czego potrzebujesz

  • Aplikację z botem w Discord Developer Portal i jego token (zakładka Bot). Jeśli bot czyta treść zwykłych wiadomości, włącz tam też uprawnienie „Message Content Intent” – bez tego pole z treścią będzie puste.
  • VPS przygotowany jak w poradniku od zera: użytkownik z sudo, logowanie kluczem, ufw. Bot nie potrzebuje otwartych portów – sam łączy się z Discordem połączeniem wychodzącym.
  • Rozmiar serwera: ani dokumentacja discord.py, ani discord.js nie podaje wymagań sprzętowych. Prosty bot na kilku serwerach Discorda to jeden proces, więc najmniejszy plan z 1 vCPU i 1 GB RAM jest rozsądnym punktem wyjścia. Więcej potrzebujesz, gdy bot gra muzykę, przetwarza obrazy albo trzyma w pamięci dane tysięcy serwerów – wtedy patrz na zużycie (systemctl status, docker stats) zamiast zgadywać.
  • Kod bota w repozytorium Git (prywatnym albo publicznym – token i tak w nim nie wyląduje).

Więcej tanich planów jest w rankingu VPS do 20 zł. Jeśli myślisz o darmowej maszynie, najpierw przeczytaj o haczykach darmowych VPS.

Krok 1. Token poza repozytorium

Token bota daje pełną kontrolę nad botem. Kto go ma, może w jego imieniu pisać, banować i usuwać kanały wszędzie tam, gdzie bot ma uprawnienia. Przewodnik discord.js mówi wprost, żeby nie commitować plików z sekretami, i podaje taki .gitignore:

tekst
node_modules
.env
config.json

W projekcie w Pythonie dopisz jeszcze katalog środowiska wirtualnego (.venv). Na serwerze token trzymasz w pliku .env, czytelnym tylko dla właściciela. Wpisz go edytorem, nie przez echo – wtedy nie zostanie w historii powłoki:

bash
sudo mkdir -p /opt/apps/discord-bot
sudo chown deploy:deploy /opt/apps/discord-bot
cd /opt/apps/discord-bot
git clone https://github.com/twoje-konto/twoj-bot.git app
nano .env          # wpisz: DISCORD_TOKEN=tu-token
chmod 600 .env

Plik .env leży obok katalogu z kodem, a nie w nim, więc git pull ani git add . go nie dotkną.

Wariant A. Python i systemd

Dokumentacja discord.py wymaga Pythona 3.8 lub nowszego. Ubuntu 24.04 i oba Debiany mają nowszego; na Ubuntu i Debianie moduł venv jest w osobnym pakiecie:

bash
sudo apt update && sudo apt install -y python3 python3-venv
cd /opt/apps/discord-bot
python3 -m venv .venv
.venv/bin/pip install -U discord.py

Bot czyta token ze zmiennej środowiskowej, nie z kodu. Minimalny przykład na wzór szybkiego startu discord.py – plik app/bot.py (nie nazywaj go discord.py, bo przesłoni bibliotekę):

python
import os
import discord

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

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

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

client.run(os.environ["DISCORD_TOKEN"])

Usługa działa jako osobny użytkownik bez powłoki, któremu katalog z kodem wystarczy tylko do odczytu:

bash
sudo useradd --system --no-create-home --shell /usr/sbin/nologin discordbot
sudo chown root:discordbot /opt/apps/discord-bot/.env
sudo chmod 640 /opt/apps/discord-bot/.env

Plik /etc/systemd/system/discord-bot.service:

ini
[Unit]
Description=Bot Discorda
After=network-online.target
Wants=network-online.target

[Service]
User=discordbot
Group=discordbot
WorkingDirectory=/opt/apps/discord-bot/app
EnvironmentFile=/opt/apps/discord-bot/.env
ExecStart=/opt/apps/discord-bot/.venv/bin/python bot.py
Restart=always
RestartSec=10
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
bash
sudo systemctl daemon-reload
sudo systemctl enable --now discord-bot
systemctl status discord-bot
journalctl -u discord-bot -f

Restart=always z RestartSec=10 podnosi bota po każdym wyjściu, a dziesięć sekund przerwy chroni przed limitem systemd, który po kilku szybkich restartach z rzędu przestaje próbować. ProtectSystem=strict montuje cały system plików tylko do odczytu – jeśli bot zapisuje dane (np. bazę SQLite), dodaj StateDirectory=discord-bot i zapisuj w /var/lib/discord-bot. client.run() w discord.py sam konfiguruje logowanie na standardowe wyjście błędów, więc wszystko trafia do journalctl.

Wariant B. Node.js w Dockerze

Ten wariant wymaga Dockera z wtyczką Compose (instalacja w poradniku od zera). Wersję Node wybierasz w obrazie, więc nie musisz instalować jej na hoście. Aktualne wydanie discord.js 14 wymaga według swojego package.json Node.js 18 lub nowszego; w przykładzie jest obraz z Node 24, czyli linią LTS.

Minimalny app/index.js w stylu przewodnika discord.js:

javascript
const { Client, Events, GatewayIntentBits } = require('discord.js');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (c) => {
  console.log(`Zalogowano jako ${c.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

app/Dockerfile (zakłada, że w repozytorium jest package-lock.json):

dockerfile
FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "index.js"]

Dodaj do .dockerignore co najmniej node_modules i .env, żeby lokalne pliki nie trafiły do obrazu. Plik /opt/apps/discord-bot/compose.yaml:

yaml
services:
  bot:
    build: ./app
    restart: unless-stopped
    env_file: .env
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
bash
cd /opt/apps/discord-bot
docker compose up -d --build
docker compose logs -f bot

env_file przekazuje token do kontenera jako zmienną, a obraz go nie zawiera – możesz go bezpiecznie zbudować ponownie albo wysłać do rejestru. Nie ma tu sekcji ports, bo bot niczego nie nasłuchuje. Lokalnie, bez Dockera, ten sam plik wczytasz flagą zalecaną w przewodniku: node --env-file=.env index.js.

Krok 2. Aktualizacja kodu

Wariant A:

bash
cd /opt/apps/discord-bot/app && git pull
/opt/apps/discord-bot/.venv/bin/pip install -U -r requirements.txt
sudo systemctl restart discord-bot

Wariant B:

bash
cd /opt/apps/discord-bot/app && git pull
cd .. && docker compose up -d --build

Jeśli bot ma komendy ukośnikowe (slash commands), po zmianie ich definicji trzeba je ponownie zarejestrować w Discordzie – zwykle osobnym skryptem albo przy starcie, zależnie od tego, jak napisałeś bota.

Najczęstsze problemy

  • Improper token has been passed albo TokenInvalid. Token jest nieaktualny (każdy reset w Developer Portal unieważnia stary) albo do .env trafiło coś poza nim, np. przypadkiem wklejony inny identyfikator z portalu (Application ID, Client Secret). Zapisz linię dokładnie jako DISCORD_TOKEN=wartość i sprawdź, czy zmienna dociera do procesu: sudo systemctl show discord-bot -p EnvironmentFiles albo docker compose exec bot printenv DISCORD_TOKEN | wc -c.
  • Bot jest online, ale nie widzi treści wiadomości. Brakuje Message Content Intent – w kodzie (intents.message_content = True albo GatewayIntentBits.MessageContent) i w Developer Portal. Musi być włączony w obu miejscach.
  • Usługa ciągle się restartuje. Zobacz przyczynę w journalctl -u discord-bot -n 50 albo docker compose logs bot. Błąd w kodzie przy starcie z Restart=always oznacza pętlę logowań, a Discord ogranicza liczbę nowych sesji bota na dobę – nie zostawiaj takiej pętli na noc.
  • Token trafił do repozytorium. Usunięcie pliku w następnym commicie nie wystarczy, token zostaje w historii. Od razu zresetuj go w Developer Portal (zakładka Bot), wpisz nowy do .env i zrestartuj bota.
  • ModuleNotFoundError: No module named 'discord' w systemd. ExecStart wskazuje systemowego Pythona zamiast tego z .venv. Podaj pełną ścieżkę do .venv/bin/python.

Co dalej

Bot to proces, którego nikt nie pilnuje, więc dołóż powiadomienie o awarii – np. Uptime Kuma z monitorem typu push, którego adres bot wywołuje co minutę; gdy wywołania ustaną, dostaniesz alert. Jeśli bot zapisuje dane, objęte kopiami powinny być /var/lib/discord-bot albo wolumeny Dockera; wysyłkę kopii poza serwer opisuje poradnik o restic i rclone. Zanim zostawisz VPS bez nadzoru, przejdź listę kontrolną zabezpieczeń.

Powiązane poradniki