Héberger un bot Discord en Python (discord.py)
Ce guide met en ligne un bot écrit avec discord.py 2.x pour qu'il tourne en continu. Le principe est le même qu'en JavaScript : un dépôt Git propre, des dépendances figées, un jeton lu dans l'environnement et un processus relancé automatiquement.
Avant de commencer
- Une application et un bot créés sur le portail développeur Discord, avec le jeton (onglet Bot).
- Les intents nécessaires cochés sur le portail et déclarés dans le code.
message_contentn'est utile que si le bot lit le texte des messages. - Un dépôt Git avec le code du bot : public (adresse en
https://), ou GitHub privé partagé avec HebergHub.
Préparer le dépôt
Structure minimale
mon-bot/
├── main.py
├── requirements.txt
├── .gitignore
└── .env.examplerequirements.txt
Fixez les versions pour que le serveur installe exactement ce que vous avez testé :
discord.py==2.7.1Après un pip install en local, pip freeze > requirements.txt produit la liste complète. Supprimez-en les paquets qui ne servent qu'au développement.
main.py
import logging
import os
import discord
from discord import app_commands
logging.basicConfig(level=logging.INFO)
TOKEN = os.environ.get("DISCORD_TOKEN")
if not TOKEN:
raise SystemExit("DISCORD_TOKEN manquant : le bot ne peut pas démarrer.")
intents = discord.Intents.default()
class MonBot(discord.Client):
def __init__(self):
super().__init__(intents=intents)
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# Synchronise les commandes slash au démarrage.
# Sur un gros bot, préférez une synchronisation manuelle.
await self.tree.sync()
bot = MonBot()
@bot.tree.command(description="Vérifie que le bot répond")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message("Pong.")
@bot.event
async def on_ready():
logging.info("Connecté en tant que %s", bot.user)
bot.run(TOKEN, log_handler=None)log_handler=None évite un double affichage, puisque la journalisation est déjà configurée par logging.basicConfig.
Le jeton hors du dépôt
# .gitignore
__pycache__/
.venv/
.envEn local, gardez le jeton dans .env et chargez-le avec python-dotenv (load_dotenv() en tête de main.py), ou exportez-le dans votre terminal. Le fichier .env.example versionné liste seulement les noms : DISCORD_TOKEN=. Voir Variables d'environnement et jetons.
Option 1 : l'hébergement applicatif
Tout se passe dans la boutique et dans votre espace client.
- Commandez la formule Application Starter (1,99 € TTC par mois, jusqu'à 2 Go de RAM et 2 vCPU). Le seul champ à remplir est le nom du projet.
- Dans votre espace client, ouvrez le service et cliquez sur « Nouvelle application » : nom, sous-domaine facultatif, Dépôt public (adresse https) ou Mes dépôts GitHub (privés), branche, type de build, port facultatif, et variables d'environnement (une par ligne,
NOM=valeur) : ajoutezDISCORD_TOKEN=votre_jeton. Les Options avancées permettent d'imposer les commandes d'installation, de build et de démarrage (par exemplepython main.py). - Cliquez sur « Déployer ». Comptez 5 à 15 minutes pour le premier déploiement ; les suivants réutilisent l'environnement mis en cache.
- Cliquez sur « Journaux » et contrôlez la ligne
Connecté en tant que ….
Si le bot plante, il est relancé automatiquement. Les boutons « Redémarrer », « Arrêter » et « Redéployer » restent à votre main, et un encadré « Pourquoi ça a échoué » résume la cause d'un déploiement raté. Pour changer le jeton, cliquez sur « Variables », modifiez la ligne, « Enregistrer », puis « Redéployer » : les variables s'appliquent au prochain redéploiement.
Le type de build Automatique (détecté dans le dépôt) reconnaît le langage du projet. Pour ne rien laisser au hasard, choisissez Dockerfile et ajoutez ce fichier à la racine :
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]PYTHONUNBUFFERED=1 fait apparaître les messages dans les journaux au moment où ils sont écrits, et non par paquets.
Option 2 : sur un VPS
Sur un VPS Debian, travaillez dans un environnement virtuel pour ne pas mélanger les paquets du bot avec ceux du système :
apt install -y python3-venv git
adduser --system --group --home /opt/mon-bot bot
git clone https://exemple.org/vous/mon-bot.git /opt/mon-bot
python3 -m venv /opt/mon-bot/.venv
/opt/mon-bot/.venv/bin/pip install -r /opt/mon-bot/requirements.txt
install -m 600 -o root -g root /dev/null /etc/mon-bot.env
nano /etc/mon-bot.env # DISCORD_TOKEN=...
chown -R bot:bot /opt/mon-bot# /etc/systemd/system/mon-bot.service
[Unit]
Description=Bot Discord (Python)
After=network-online.target
Wants=network-online.target
[Service]
User=bot
WorkingDirectory=/opt/mon-bot
EnvironmentFile=/etc/mon-bot.env
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mon-bot/.venv/bin/python main.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload
systemctl enable --now mon-bot
journalctl -u mon-bot -fSéquence vérifiée sur un VPS conteneur et sur un VPS machine virtuelle Debian 13 (Python 3.13.5) : venv, installation des dépendances, unité systemd, démarrage. Avec un jeton invalide, le service échoue proprement (discord.errors.LoginFailure, code de sortie 1) et systemd le relance toutes les 5 secondes ; le service démarre de lui-même après un redémarrage.
Attention à la version de discord.py : sur Debian 13 (Python 3.13), la version 2.4.0 ne démarre pas (ModuleNotFoundError: No module named 'audioop'). Utilisez une version récente, 2.7.1 ici, qui installe automatiquement le paquet audioop-lts. Pour connaître les versions disponibles : pip index versions discord.py.
Mettre à jour
- Applicatif : poussez sur la branche suivie. Un dépôt GitHub privé partagé avec HebergHub est redéployé automatiquement à chaque push ; pour un dépôt public, cliquez sur « Redéployer ». « Reconstruire à neuf » repart sans cache si besoin.
- VPS :
git pull, réinstallez les dépendances sirequirements.txta changé, puissystemctl restart mon-bot.
Problèmes fréquents
| Symptôme | Cause probable |
|---|---|
discord.errors.LoginFailure: Improper token |
Jeton absent, tronqué ou régénéré. |
PrivilegedIntentsRequired |
Intent demandé dans le code mais non coché sur le portail. |
| Aucun message dans les journaux | Sortie mise en tampon : ajoutez PYTHONUNBUFFERED=1 (dans le Dockerfile, ou dans « Variables » sur l'applicatif). |
ModuleNotFoundError |
Paquet absent de requirements.txt. |
| Commandes slash absentes | tree.sync() non appelé, ou délai de propagation des commandes globales. |
Besoin d'aide ?
Ouvrez un ticket depuis votre espace client avec le nom de l'application et le message d'erreur. Ne communiquez jamais votre jeton.