Docker sur un VPS : qu’est-ce qui change vraiment ?
Le moteur Docker reste identique, mais un petit VPS manque vite de RAM, les ports publiés contournent UFW, les conteneurs s’arrêtent au reboot et le disque se remplit.
Ce qui change lorsque vous exécutez Docker sur un VPS
Docker sur un VPS utilise le même moteur et les mêmes images que Docker sur votre ordinateur portable. Toutes les commandes que vous connaissez déjà fonctionnent donc toujours. Ce qui change, c’est l’environnement disponible. Un ordinateur portable dispose de mémoire libre, d’un pare-feu que personne ne sonde et d’un disque suffisamment grand pour que vous n’ayez jamais à le surveiller. Un serveur loué a une limite de mémoire fixe, une adresse IP publique sondée quelques minutes après le démarrage et un système de fichiers racine que Docker remplira sans vous demander votre avis.
Quatre différences causent la plupart des problèmes sur une petite machine :
- La mémoire est limitée, et le kernel résout une pénurie en tuant un processus.
- Un port publié passe directement à travers UFW (uncomplicated firewall), car Docker écrit ses propres règles de pare-feu.
- Les conteneurs ne redémarrent pas après un reboot, sauf si vous l’avez demandé à l’avance.
- Les images, les conteneurs, les volumes et le build cache grossissent jusqu’à saturer le disque.
Chaque section ci-dessous indique la panne, le message que vous verrez réellement et le guide qui la corrige en détail. Si vous n’avez pas encore écrit de fichier Compose, lisez d’abord les bases de Docker Compose sur un VPS, puis revenez ici. Cette page suppose que vous savez déjà démarrer une stack.
Combien de RAM un conteneur Docker utilise-t-il ?
Moins que la plupart des utilisateurs ne le pensent. Un conteneur est un processus placé dans un cgroup (control group), pas une machine virtuelle. Il n’y a donc ni kernel invité ni allocation fixe. La consommation correspond à ce que le processus à l’intérieur utilise réellement. C’est pourquoi une stack complète tient dans 2 GB alors que la même stack, construite avec des machines virtuelles, ne tiendrait pas.
Les valeurs ci-dessous correspondent à des consommations typiques au repos pour des images standard sur Ubuntu 24.04 avec la configuration par défaut. Elles sont relevées dans docker stats quelques minutes après le démarrage. Elles servent de base pour planifier les ressources, pas de benchmark de votre charge de travail. Exécutez docker stats --no-stream sur votre propre serveur avant de vous fier à une valeur, y compris à celles-ci.
Les données de ce graphique
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]Les deux colonnes ont des fonctions différentes. idle_mb indique ce que le conteneur utilise lorsqu’il ne fait rien. budget_mb indique la quantité à réserver lors de la planification, car l’utilisation réelle ne se limite pas à l’état d’inactivité. PostgreSQL utilise environ 45 MB au repos et a besoin de 512 MB lorsque les connexions, les tris et le cache sont actifs. Planifiez avec la colonne du budget. Effectuez le diagnostic avec la colonne de consommation au repos.
Observez la répartition de ces 7 lignes. nginx utilise 8 MB au repos et Nextcloud 210 MB. Le proxy placé devant vos applications consomme presque rien. C’est pour la base de données et l’application PHP que vous devez dimensionner le serveur.
Attention à docker stats : la valeur de mémoire inclut le cache de pages chargé par les lectures de fichiers du conteneur. Elle augmente donc pendant un certain temps après le démarrage, puis se stabilise. Surveillez-la pendant une heure avant de conclure à une fuite mémoire.
Dimensionnement d’un VPS : que peut-on faire avec 2 GB, 4 GB et 8 GB
Commencez par soustraire la part de l’hôte. Le kernel, systemd, journald, sshd et le daemon Docker utilisent la même RAM que vos conteneurs, et dockerd avec containerd en occupent environ 100 MB. Vous devez également conserver de la mémoire libre pour le page cache et pour le pic de consommation lors de la construction d’une image ou de l’exécution d’un dump de base de données.
Les données de ce graphique
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb couvre le système d’exploitation, le daemon Docker et la marge nécessaire pour que le serveur reste réactif sous charge. Le reste correspond à container_mb, et c’est le seul montant que vous pouvez allouer. La réserve augmente avec l’offre, de 768 MB sur le plus petit serveur à 1536 MB sur le plus grand, car un serveur plus puissant exécute davantage de conteneurs, écrit davantage de journaux et utilise davantage de page cache.
Une offre de 2 GB laisse 1280 MB pour les conteneurs. Allouez 512 MB à PostgreSQL et 128 MB à Traefik : la moitié de la mémoire est déjà utilisée. Il reste alors deux petites applications d’environ 256 MB chacune. C’est un serveur réel et utile. En revanche, il ne peut pas accueillir Nextcloud et un cluster de recherche en plus.
Une offre de 4 GB laisse 3072 MB. Cette capacité suffit pour une base de données, un reverse proxy, trois applications et un conteneur de monitoring exécutés simultanément. C’est la plus petite taille qui vaille la peine pour un service important, car la mémoire disponible absorbe les effets d’un déploiement défectueux.
Une offre de 8 GB laisse 6656 MB sur ses 8192 MB. La limite vient alors généralement du CPU ou du débit disque plutôt que de la mémoire. SSD Nodes ne vend rien de plus petit : son offre minimale dispose de 8 GB de RAM (page tarifaire consultée le 2026-10-11). Sur cet hôte, c’est donc cette ligne qui s’applique. Les lignes 2 GB et 4 GB décrivent des serveurs plus petits proposés par d’autres fournisseurs. Certains conteneurs dimensionnent leur consommation à partir de leur configuration plutôt qu’à partir de la charge : un serveur de modèle local réserve le KV cache en fonction de la taille de sa fenêtre de contexte. Ainsi, augmenter num_ctx dans Ollama peut ajouter plusieurs gigaoctets au budget avant l’arrivée de la moindre requête. Si les calculs montrent que votre stack ne tient pas, prenez l’offre supérieure au lieu de multiplier les réglages : ce que coûte réellement un VPS explique ce que valent les gigaoctets supplémentaires chaque mois.
Deux règles permettent de conserver un calcul réaliste. Définissez une limite de mémoire pour chaque service afin qu’un processus qui s’emballe ne puisse pas épuiser toute la mémoire du serveur. Et ne dépensez pas toute la mémoire disponible, car docker compose build et pg_dump en ont tous deux besoin au pire moment. Limites de mémoire dans Docker Compose présente la syntaxe et les pièges à éviter.
Pourquoi mon conteneur s’arrête-t-il avec le code 137 ?
Parce que le kernel l’a tué. 137 correspond à 128 plus 9, et le signal 9 est SIGKILL. Le conteneur a demandé plus de mémoire que sa limite ne l’autorisait, puis l’out of memory (OOM) killer l’a arrêté.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoConfirmez la cause au lieu de la supposer :
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true signifie que le conteneur a atteint sa propre limite cgroup, et que le journal du kernel indique le processus sélectionné :
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBC’est le bon cas, car le problème reste limité à un seul conteneur. Le mauvais cas concerne un conteneur sans aucune limite. Sans limite, son plafond correspond à toute la machine. Une fuite mémoire dans un service prive alors l’hôte de mémoire, puis le kernel choisit une victime selon sa consommation à l’échelle du système. La ligne du journal ne contient plus le préfixe Memory cgroup et affiche Out of memory: Killed process 2417 (postgres). Le processus sélectionné est souvent votre base de données, tandis que le conteneur à l’origine de la fuite continue de fonctionner. C’est pourquoi il est plus important de définir une limite pour chaque service que de choisir précisément la valeur d’une seule limite.
Le swap change le moment où le problème survient, pas les calculs. La plupart des images VPS sont fournies sans swap. Vérifiez avec swapon --show, qui n’affiche absolument rien lorsqu’il n’y en a pas. Un fichier swap fournit au kernel un emplacement où placer les pages froides, ce qui vous laisse quelques minutes pour détecter le problème.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h doit maintenant afficher un total non nul sur la ligne Swap. Le swap n’ajoute pas de RAM. Une machine soumise à une pression mémoire constante devient suffisamment lente pour vous empêcher de vous y connecter en SSH afin de corriger le problème. Considérez donc le swap comme un tampon d’alerte et corrigez le dimensionnement.
Pourquoi UFW ne bloque-t-il pas mon port Docker publié ?
Parce que le trafic n’atteint jamais la chaîne que UFW protège. Lorsque vous publiez un port avec -p 5432:5432 ou avec une entrée Compose ports:, le daemon ajoute une règle DNAT (translation d’adresse réseau de destination) dans la table nat et une règle d’acceptation dans sa propre chaîne DOCKER. Un paquet destiné à un conteneur est transféré vers ce conteneur au lieu d’être remis à l’hôte. Il suit donc le chemin FORWARD et ne passe jamais par les règles INPUT ajoutées par UFW.
Vous pouvez observer ce comportement sur le serveur :
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW peut afficher 5432 DENY IN Anywhere alors que la table nat contient une règle DNAT tcp ... to:172.18.0.2:5432 pour le même port. Depuis une autre machine, nc -vz your.server.ip 5432 se connecte toujours. La base de données est accessible depuis Internet, même si le pare-feu indique le contraire.
La solution consiste à publier moins de ports. Les conteneurs d’un même projet Compose partagent un réseau et peuvent communiquer entre eux avec le nom du service. Une base de données qui sert uniquement l’application voisine n’a donc besoin d’aucune entrée ports:. Si vous avez besoin d’un accès local, liez la publication à loopback :
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"Après docker compose up -d, nc -vz your.server.ip 5432 échoue depuis l’extérieur, tandis que psql -h 127.0.0.1 -p 5432 fonctionne toujours sur le serveur. Dans une petite stack correctement configurée, seul le reverse proxy publie des ports, sur 80 et 443. Pourquoi les ports Docker publiés contournent UFW présente la chaîne DOCKER-USER pour les cas où vous devez publier un port tout en le filtrant, et Bases du pare-feu UFW présente les règles de l’hôte situées en dessous.
Pourquoi mes conteneurs ont-ils disparu après un redémarrage ?
Parce que rien ne leur a demandé de redémarrer. Un conteneur est créé avec la policy de redémarrage no si vous n’en définissez pas. Après un redémarrage, il reste donc arrêté et le daemon ne s’en préoccupe pas. Les redémarrages ne sont pas rares sur un VPS : les mises à jour du kernel via unattended upgrades, la maintenance du fournisseur et la séquence OOM décrite plus haut peuvent tous en provoquer un.
Deux conditions doivent être réunies. Le daemon doit démarrer au boot :
systemctl is-enabled dockerCette commande affiche enabled sur une installation Ubuntu standard. Chaque service doit ensuite avoir une policy :
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedunless-stopped redémarre le conteneur après un reboot, tout en respectant un conteneur que vous avez arrêté volontairement. always redémarre également les conteneurs que vous avez arrêtés délibérément lorsque le daemon redémarre, ce qui peut être surprenant pendant un dépannage. Modifier le fichier ne suffit pas, car la policy de redémarrage est définie lors de la création du conteneur. Exécutez docker compose up -d pour le recréer, puis vérifiez la valeur active :
docker inspect my-app | grep -A3 RestartPolicyRedémarrez ensuite volontairement le serveur et exécutez docker compose ps dans le répertoire du projet. Une stack qui survit à un redémarrage planifié survivra aussi à un redémarrage imprévu. Si votre stack a besoin d’un ordre de démarrage garanti ou d’un job exécuté une seule fois au boot, une unité systemd est préférable : démarrer Docker Compose au boot contient le fichier d’unité. Pour vérifier qu’un conteneur redémarré fournit réellement le service attendu, ajoutez des healthchecks Compose.
Pourquoi le disque de mon VPS est-il plein ?
Parce que Docker conserve tout tant que vous ne lui demandez pas de nettoyer. Chaque tag d’image que vous avez téléchargé, chaque conteneur arrêté, chaque volume anonyme laissé après une recréation et chaque couche du cache de build restent sur le disque. Un disque plus grand ne fait que retarder le problème. Même le SSD de 160 GB du plus petit plan de SSD Nodes (page tarifaire vérifiée le 2026-10-11) se remplit de la même façon sur une machine qui télécharge et construit des images chaque semaine. Avec un système de fichiers root plus petit, la panne survient simplement plus tôt.
Un disque plein ne ressemble pas à un crash. Vous recevez no space left on device d’un conteneur, de apt, de journald et de docker pull au cours de la même heure. PostgreSQL cesse d’accepter les écritures. La machine reste opérationnelle, ce qui rend le problème plus difficile à remarquer qu’une boucle de redémarrage.
Vérifiez avant de supprimer :
docker system df
df -h /docker system df répartit l’espace total entre les images, les conteneurs, les volumes locaux et le cache de build, avec une colonne RECLAIMABLE pour chacun. Sur une machine qui construit ses propres images, le cache de build est généralement la ligne la plus importante.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a supprime toutes les images qu’aucun conteneur n’utilise. docker builder prune vide le cache de build. Les deux opérations peuvent être effectuées pendant que les services fonctionnent, car les éléments utilisés sont ignorés. En revanche, docker system prune --volumes est risqué. Il supprime les conteneurs arrêtés, puis chaque volume anonyme qu’aucun conteneur restant n’utilise. Une stack arrêtée pour le week-end présente exactement cette configuration. Si sa base de données stockait ses données dans un volume anonyme, ce qui se produit lorsqu’une image déclare un volume et que votre fichier compose n’y monte rien, ces données sont supprimées avec lui. Les volumes nommés survivent à cette commande, mais docker volume prune --all supprime également les volumes nommés inutilisés. Lisez montages bind et volumes nommés avant d’utiliser l’un ou l’autre flag, et effectuez d’abord une sauvegarde.
Les logs des conteneurs grossissent plus discrètement. Le driver json-file utilisé par défaut n’a aucune limite de taille. Un conteneur très bavard peut donc écrire plusieurs gigaoctets dans /var/lib/docker/containers. Limitez leur taille pour chaque conteneur dans /etc/docker/daemon.json :
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Appliquez la modification avec sudo systemctl restart docker. Cette commande redémarre vos conteneurs : choisissez donc le bon moment. La limite s’applique aux conteneurs créés après la modification. Recréez donc les conteneurs en cours d’exécution avec docker compose up -d --force-recreate, puis vérifiez :
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersLa sortie de inspect doit indiquer que max-size est défini. Si le champ est vide, ce conteneur existait avant la modification et continue d’écrire sans limite.
Habitudes qui permettent de maintenir un petit serveur Docker en bon état
Aucune de ces pratiques ne nécessite un dashboard ni un outil à apprendre.
- Exécutez
docker system dfetdf -h /le premier jour du mois. Deux commandes, trente secondes, et vous voyez la tendance bien avant qu’elle ne provoque une panne. - Définissez une limite de mémoire pour chaque service, y compris ceux que vous pensez être petits. La limite transforme une panne de l’hôte entier en un seul conteneur redémarré.
- Surveillez le serveur depuis un autre emplacement, afin d’être informé de la pression sur la mémoire ou le disque avant que le kernel n’intervienne. Uptime Kuma s’exécute dans un conteneur et utilise environ 95 MB au repos.
- Sauvegardez les volumes, pas les conteneurs. Le conteneur est jetable, le volume ne l’est pas. sauvegardes restic sur un VPS couvre la planification des sauvegardes et un test de restauration.
- Épinglez les tags d’image dans le fichier compose et mettez-les à jour le jour de votre choix. Avec
latest, la version obtenue lors du prochaindocker compose pullest celle publiée ce matin-là.
Un petit VPS qui exécute Docker reste en bon état pendant des années lorsque quatre valeurs restent dans la plage prévue : le budget mémoire, la liste des ports publiés, la politique de redémarrage de chaque service et l’espace disque disponible. Pour le reste, c’est le même Docker que celui que vous utilisez déjà chez vous.
FAQ
De combien de RAM ai-je besoin pour exécuter Docker sur un VPS ?
Docker lui-même consomme peu de ressources. Le daemon et containerd utilisent ensemble environ 100 MB. Le reste dépend de vos conteneurs. Réservez d’abord la part de l’hôte : 768 MB sur une machine de 2048 MB pour le système d’exploitation, le daemon et une marge de sécurité. Il reste alors 1280 MB pour les conteneurs. Une base de données à 512 MB, un reverse proxy à 128 MB et deux petites applications tiennent dans cette limite. Mesurez votre propre stack avec docker stats --no-stream au lieu de vous fier à une valeur publiée.
Puis-je exécuter Docker sur un VPS de 1 GB ?
Oui, pour un ou deux conteneurs légers. Ajoutez un fichier swap avant de commencer. Environ la moitié d’une machine de 1 GB est déjà utilisée lorsque le système d’exploitation et le daemon Docker sont lancés. Il reste assez de ressources pour une petite application et un reverse proxy, mais pas pour une base de données soumise à une charge réelle. La construction d’images sur une machine de cette taille échouera ou provoquera l’arrêt d’un autre service. Construisez donc les images ailleurs, puis récupérez l’image terminée.
UFW protège-t-il un conteneur Docker ?
Pas pour les ports que vous publiez. Docker écrit ses propres règles DNAT et de forwarding. Un paquet destiné à un port publié d’un conteneur est donc transféré vers le conteneur au lieu d’être remis à l’hôte. Les règles INPUT gérées par UFW ne le voient jamais. ufw deny 5432 peut être actif alors que ce port répond depuis Internet. Publiez le port sur la loopback avec 127.0.0.1:5432:5432, ne publiez pas les services internes, ou filtrez dans la chaîne DOCKER-USER.
Mes conteneurs redémarreront-ils après le redémarrage d’un VPS ?
Uniquement s’ils ont été créés avec une restart policy. Définissez restart: unless-stopped pour chaque service, exécutez docker compose up -d afin de recréer les conteneurs avec cette policy, puis vérifiez que systemctl is-enabled docker affiche enabled. Redémarrez ensuite volontairement le serveur et contrôlez docker compose ps. Une restart policy que vous n’avez jamais testée n’est pas une restart policy fiable.
À quelle fréquence dois-je supprimer les images Docker inutilisées ?
Une fois par mois suffit pour la plupart des petits serveurs. Vous pouvez aussi le faire lorsque docker system df signale un espace récupérable dont vous avez besoin. docker image prune -a et docker builder prune sont sans danger pendant l’exécution des services, car les images et le cache utilisés sont ignorés. Évitez docker system prune --volumes, sauf si vous savez exactement quels volumes ne sont référencés par aucun conteneur. Cette commande supprime les volumes anonymes de toute stack arrêtée, et docker volume prune --all supprime également les volumes nommés inutilisés.