Aller au contenu principal

Ajouter des services à côté d'un bot existant

Supposons qu’un VPS héberge déjà un bot Discord et que vous souhaitiez ajouter RSSHub, une API personnelle ou un site web. Il faut garder des limites claires pour la configuration, le cycle de vie, le réseau et les données de chaque service. Pouvoir lancer un conteneur de plus avec Docker ne suffit pas à régler ces questions.

Privilégier un projet Compose par application​

Par exemple, chaque application peut avoir son propre répertoire :

  • /srv/discord-bot/ : compose.yaml, un fichier .env exclu de Git et data/.
  • /srv/rsshub/ : compose.yaml et .env.
  • /srv/example-api/ : compose.yaml et data/.
  • /srv/caddy/ : compose.yaml et Caddyfile.

Compose tire normalement le nom du projet du répertoire, ce qui regroupe ses conteneurs, son réseau par défaut et les noms de ses ressources, tout en isolant les ressources et les réseaux par défaut des différents projets. Vous pouvez aussi définir name: discord-bot au premier niveau du fichier Compose. Avec des projets séparés, mettre à jour le bot ne recrée pas RSSHub, et le périmètre des journaux et du retour arrière reste clair.

Un grand fichier compose.yaml commun peut convenir, mais il rattache des services indépendants au même cycle de vie. Pour un serveur personnel sur une seule machine, des projets séparés sont généralement un point de départ plus facile à comprendre.

Déterminer d’abord si le service a besoin d’un port entrant​

Un bot utilisant Discord Gateway établit généralement une connexion WebSocket sortante persistante vers Discord. S’il utilise uniquement la Gateway, il n’a normalement besoin d’aucun port ouvert sur l’adresse publique du VPS :

/srv/discord-bot/compose.yaml
name: discord-bot

services:
bot:
image: ghcr.io/example/discord-bot:<pinned-version>
restart: unless-stopped
env_file:
- .env
volumes:
- ./data:/app/data

Un bot qui reçoit des interactions HTTP ou des webhooks a besoin d’une route entrante ; une interface d’administration peut aussi nécessiter un accès HTTP entrant. Dans ce cas, privilégiez Caddy comme unique point d’entrée public plutôt que de publier des ports arbitraires pour chaque conteneur.

Trois types d’accès réseau​

Type de serviceAccès recommandéExemple
Worker/bot avec connexions sortantes uniquementAucune entrée portsBot Discord Gateway, collecteur périodique
Accès réservé à l’administrationLiaison au bouclage local ou à Tailscale127.0.0.1:3000:3000, ou tailnet uniquement
Site web/API publicReverse proxy Caddy sur 80/443Ne pas exposer directement le port applicatif

Pour que Caddy accède à une application d’un autre projet Compose, créez un réseau Docker externe nommé explicitement :

docker network create ingress

Chaque projet déclare ce réseau :

networks:
ingress:
external: true

Rattachez à ingress uniquement les services auxquels le proxy doit accéder. Les bases de données et les caches doivent rester sur les réseaux internes de leurs projets, sans publication de ports de base de données sur Internet.

Un modèle d'entrée partagée validé​

Les phases 2 et 3 sur ce VPS ont mis le modèle général en pratique :

Internet -> hôte TCP 80/443 -> Caddy -> infra-edge -> RSSHub:1200
WSL2 -> Tailscale SSH -> VPS 127.0.0.1:3001 -> Uptime Kuma

Caddy est le seul service Docker publié sur les adresses publiques de l'hôte. Le pont externe réutilisable s'appelle infra-edge ; Caddy et RSSHub en font partie, et Caddy atteint RSSHub via l'alias rsshub-prod. RSSHub ne publie aucun port hôte. Uptime Kuma reste délibérément en dehors du chemin d'entrée public et publie exactement 127.0.0.1:3001:3001.

AccessibilitéPort hôte accepté
PublicTCP 80 et TCP 443, gérés par Caddy
Tailnet uniquementTCP 22 via tailscale0
Bouclage local (loopback) du VPS uniquementTCP 3001, géré par Uptime Kuma
Réseau Docker uniquementRSSHub TCP 1200, non publié sur l'hôte
Tout le resteAucune publication hôte Docker

Ouvrez l’interface de supervision par un tunnel de redirection locale, sans ajouter de route publique :

[LOCAL: WSL2]
ssh -i "$HOME/.ssh/id_ed25519" \
-o IdentitiesOnly=yes \
-L 3001:127.0.0.1:3001 \
"$VPS_USER@$TAILSCALE_IPV4"

Consultez ensuite http://127.0.0.1:3001. Le tunnel protège la route de gestion sans publier d'identifiants, de cookies ou l'identité de l'administrateur.

UFW ne constitue pas l'audit d'exposition de Docker​

Un jeu de règles UFW actif est nécessaire, mais ne prouve pas qu'un port publié par Docker soit privé. La documentation officielle de Docker sur le pare-feu explique que le trafic des conteneurs publiés est détourné avant les chaînes normalement utilisées par UFW. La liaison dans Compose fait donc partie de la frontière de sécurité : incluez explicitement 127.0.0.1 pour un service réservé au bouclage local, et vérifiez le résultat depuis Docker, l'hôte et une autre machine.

Vérifiez la version de Docker Engine avant de réutiliser ce modèle d'isolation. Docker indique que, dans les versions antérieures à 28.0.0, les hôtes du même segment réseau de couche 2 pouvaient accéder aux ports publiés sur localhost. Avec ces versions, une liaison à 127.0.0.1 ne suffit pas à elle seule à prouver l'isolation.

[REMOTE: VPS]
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
sudo ss -lntup
sudo ufw status verbose
sudo ufw status numbered
docker network inspect infra-edge

KUMA_ID=$(docker compose -f /srv/uptime-kuma/compose.yaml ps -q uptime-kuma)
docker inspect "$KUMA_ID" --format '{{json .HostConfig.PortBindings}}'

RSSHUB_ID=$(docker compose -f /srv/rsshub/compose.yaml ps -q rsshub)
docker inspect "$RSSHUB_ID" --format '{{json .HostConfig.PortBindings}}'

La valeur d'inspection acceptée pour Kuma est :

{"3001/tcp":[{"HostIp":"127.0.0.1","HostPort":"3001"}]}

La table des liaisons acceptée pour RSSHub est {}. Si docker ps affiche seulement 1200/tcp, il s’agit d’un port interne au conteneur, pas d’une publication sur l’hôte.

La mention 443/udp ou 2019/tcp dans la liste des ports affichés d’une image Caddy ne signifie pas en soi que l’hôte publie ces ports. Il y a exposition sur l’hôte uniquement lorsque le mappage Compose, docker inspect ou les sockets de l’hôte indiquent une liaison HostIP/HostPort.

Depuis WSL2, vérifiez à la fois les chemins autorisés et refusés. Une réponse sur l'IP publique sur le port 3001 ou une connexion SSH réussie sur l'IP publique constitue un échec de validation.

[LOCAL: WSL2]
curl --fail https://infra.example.com/healthz
curl --connect-timeout 5 --max-time 7 http://"$VPS_PUBLIC_IPV4":3001/
curl --connect-timeout 5 --max-time 7 http://"$VPS_PUBLIC_IPV4":1200/
curl --connect-timeout 5 --max-time 7 http://"$TAILSCALE_IPV4":1200/
ssh -o ConnectionAttempts=1 -o ConnectTimeout=5 \
"$VPS_USER@$VPS_PUBLIC_IPV4"

La phase 2 a utilisé un redémarrage complet délibéré pour prouver le socle de l'hôte. Une application nouvellement ajoutée doit d'abord faire l'objet d'un redémarrage contrôlé plus restreint : redémarrez uniquement ce service Compose, attendez l'état sain, puis répétez les vérifications des points de terminaison, du contrôle d'accès, des liaisons Docker et des dépendances. Ne redémarrez l'ensemble de l'hôte que lorsqu'une tâche ultérieure nécessite spécifiquement une preuve de récupération au niveau de l'hôte.

Secrets et configuration​

Le token d’un bot Discord est un secret sensible :

  • Le conserver dans un fichier .env aux permissions restreintes ou dans un gestionnaire de secrets adapté.
  • Ajouter .env à .gitignore et ne jamais le committer.
  • Ne pas exposer les valeurs développées dans la sortie de docker compose config, les captures d’écran ou les journaux partagés.
  • En cas de suspicion de fuite, renouveler le token dans le Discord Developer Portal ; le supprimer d’un fichier ne suffit pas.

Par exemple :

cd /srv/discord-bot
touch .env
chmod 600 .env

Dans les notes, ne consignez que les noms de variables, comme DISCORD_TOKEN, jamais leurs valeurs réelles.

Procédure d'ajout répétable​

1. Inventorier les ressources et les conflits​

docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker network ls
docker volume ls
ss -lntup
free -h
df -h /

Vérifiez que les ports nécessaires sont libres, que la mémoire et le disque ont de la marge, et que l’emplacement des données du nouveau service est connu.

2. Créer un répertoire distinct​

# Créer d’abord /srv/new-service selon la procédure avec contrôles du guide d’exploitation.
# Y créer et vérifier compose.yaml ainsi que les fichiers de secrets nécessaires.
cd /srv/new-service || exit 1
test -f compose.yaml || exit 1
mkdir -p data

3. Vérifier la configuration Compose résolue​

docker compose -f ./compose.yaml config --quiet
docker compose config --services
docker compose config --images

docker compose config peut développer les variables d’environnement. Ne copiez pas sa sortie complète dans un espace public.

4. Télécharger les images et démarrer​

docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail 100

En production, privilégiez une version précise ou un digest plutôt que de vous fier aveuglément à latest. Une mise à jour d’image peut introduire des changements incompatibles.

5. Vérifier la santé du service lui-même​

L’état running indique seulement que le processus principal du conteneur ne s’est pas arrêté. Vérifiez aussi le comportement de l’application : le bot doit se connecter à la Gateway et répondre à une commande de test, un service HTTP doit renvoyer le contenu attendu via Caddy, et les données doivent être écrites dans le volume prévu.

6. Consigner un point de retour arrière​

Avant une mise à jour, notez la version actuelle de l’image et le diff Compose. Le retour arrière consiste généralement à restaurer l’ancienne version, relancer docker compose up -d et rétablir des données compatibles. Si une migration a modifié le format de la base de données, revenir à l’ancienne image peut ne pas suffire.

Exploiter chaque projet séparément​

cd /srv/discord-bot
docker compose ps
docker compose logs -f --tail 100
docker compose pull
docker compose up -d

cd /srv/rsshub
docker compose ps

Depuis n’importe quel répertoire, vous pouvez aussi utiliser :

docker compose -f /srv/discord-bot/compose.yaml ps

Depuis le répertoire /srv ou tout autre répertoire où le projet visé est incertain, ne lancez pas sans vérification docker compose down. N’utilisez pas docker system prune -a pour résoudre un problème courant de disque ou de déploiement. La première commande risque de viser le mauvais projet ; la seconde effectue un nettoyage étendu.

Mettre à jour un service sans toucher aux autres​

cd /srv/discord-bot
docker compose pull bot
docker compose up -d --no-deps bot
docker compose ps
docker compose logs --tail 100 bot

Seul le service nommé est recréé. Vérifiez néanmoins les dépendances, les migrations de base de données et les notes de version une à une : --no-deps ne garantit pas à lui seul une mise à jour sans risque.

Quand envisager de regrouper des projets​

Regroupez deux composants dans un projet Compose uniquement s’ils partagent le même cycle de publication, doivent démarrer et revenir à la version précédente ensemble, et relèvent de la même configuration. Une application et son worker dédié peuvent partager un projet ; un bot Discord et un RSSHub indépendant n’ont généralement pas besoin d’être regroupés.

À mesure que le serveur grandit, des limites claires épargnent plus de travail que quelques fichiers YAML en moins. Le nombre de conteneurs n’est pas le problème ; ne plus savoir quelle commande redémarre quel service l’est.

Le déploiement miroir de RSSHub en est un exemple concret sur ce serveur : image officielle fixée par digest, un fichier d’environnement protégé, cache en mémoire, infra-edge, aucun port hôte, canari authentifié et procédure de retour arrière au périmètre défini.

Explorer les liensOuvrir le réseau