Auto-héberger ERPNext sur un VPS avec Docker
Déployez ERPNext sur votre VPS avec Docker : 11 conteneurs, dimensionnement, TLS, e-mails sortants, versions épinglées et restauration testée.
Ce que vous vous apprêtez à exécuter
Auto-héberger ERPNext sur un VPS est une tâche d’exploitation, pas une installation en une seule commande. La stack Docker Compose officielle comprend onze conteneurs et contient votre grand livre ainsi que les fiches de vos clients. Cela impose un niveau d’exigence élevé pour tout ce qui suit : une sauvegarde n’en est pas une tant que vous ne l’avez pas restaurée, et un tag d’image non épinglé peut entraîner une migration de schéma imprévue.
Plusieurs noms reviennent dans ce guide. ERPNext est l’application métier. Frappe est le framework Python sous-jacent. Bench est l’outil en ligne de commande qui gère les sites ; il est déjà installé dans les conteneurs. Un site est un tenant : une base de données MariaDB et un répertoire de fichiers téléversés. Presque toutes les commandes de ce guide s’exécutent bench dans le conteneur backend sur un site nommé.
Ce guide utilise le dépôt frappe_docker, qui correspond au déploiement maintenu par le projet. Toutes les commandes ci-dessous ont été vérifiées avec ce dépôt en août 2026. Si Docker Compose vous est encore peu familier, la page exécuter Docker Compose sur un VPS présente les notions supposées acquises dans ce guide.
De combien de ressources VPS ERPNext a-t-il besoin ?
The data behind this chart
[
{
"label": "Evaluation",
"vcpu": 2,
"ram_gb": 4,
"disk_gb": 40
},
{
"label": "Small production",
"vcpu": 4,
"ram_gb": 8,
"disk_gb": 100
},
{
"label": "Room to grow",
"vcpu": 4,
"ram_gb": 16,
"disk_gb": 160
}
]Les recommandations publiées commencent à 2 vCPU et 4 Go de RAM avant même la connexion d’un seul utilisateur. Il s’agit du niveau d’évaluation. Ce sont des points de départ, pas des mesures issues de ce guide. Le volume de vos documents détermine le nombre réel de ressources nécessaires. La dernière ligne ne correspond pas du tout à un minimum publié. Elle indique approximativement le niveau à partir duquel la mémoire cesse d’être une contrainte à surveiller en permanence.
Soyez réaliste avec les petits forfaits. Un VPS de 1 Go ou 2 Go démarre la stack, puis s’arrête au premier import ou au premier rapport long, car neuf conteneurs persistants, le buffer pool de MariaDB et un worker Python qui génère un rapport ne tiennent pas dans cette mémoire. L’échec n’est pas géré proprement. Le kernel out of memory killer arrête un conteneur, puis docker inspect affiche "OOMKilled": true avec le code de sortie 137. Un worker arrêté au milieu d’un job laisse un document soumis avec son traitement en arrière-plan partiellement terminé.
Pour une entreprise qui utilise ERPNext quotidiennement, 8 Go de RAM, 4 vCPU et 100 Go de SSD constituent le minimum réaliste. La RAM arrive à saturation en premier. Le disque se remplit plus vite que prévu, car chaque pièce jointe et chaque sauvegarde locale sont stockées sur le même volume que la base de données.
Les onze conteneurs et le rôle de chacun
Exécutez docker compose ps une fois la stack démarrée et les neuf conteneurs en fonctionnement. Deux autres conteneurs, configurator et create-site, exécutent leur tâche une fois puis s’arrêtent. C’est ce qui explique le total de onze conteneurs.
backendexécute l’application Frappe avec gunicorn. C’est là que s’exécutebench.frontendest nginx. Il sert les ressources statiques et transmet tout le reste au backend.queue-shortetqueue-longsont des workers RQ (Redis Queue). Ils exécutent les tâches en arrière-plan, comme l’envoi d’e-mails, les imports et la génération de rapports.schedulerdéclenche les tâches planifiées, notamment les rapports planifiés et les documents récurrents automatiques.websocketest le processus socket.io qui gère les mises à jour en temps réel dans le navigateur.dbest MariaDB.redis-cacheetredis-queuesont deux instances Redis distinctes : l’une pour le cache et l’autre pour la file d’attente des tâches.
Cette séparation est importante à comprendre, car elle indique quel journal consulter. Un e-mail bloqué relève d’un problème du worker de file d’attente ; docker compose logs -f queue-short est donc la commande appropriée. Une page qui se charge mais dont le badge de notification ne se met jamais à jour indique un problème de websocket. Consulter les journaux de backend pour l’un ou l’autre de ces problèmes vous ferait perdre beaucoup de temps.
Installer avec les fichiers Compose de production, pas avec la démonstration
Le dépôt fournit pwd.yml, et le README est explicite : « Cette configuration est uniquement destinée à une évaluation de courte durée. Vous ne pourrez pas installer d’applications personnalisées avec cette configuration. » Utilisez-la pour découvrir ERPNext pendant une après-midi. Ne l’utilisez pas pour exploiter le système d’information d’une entreprise.
sudo apt update && sudo apt install -y git
curl -fsSL https://get.docker.com | bash
git clone https://github.com/frappe/frappe_docker
cd frappe_docker
mkdir -p ~/gitops
cp example.env ~/gitops/erpnext.envOuvrez ~/gitops/erpnext.env et modifiez quatre valeurs. ERPNEXT_VERSION fixe le tag de l’image. DB_PASSWORD est fourni sous la forme 123 dans le fichier d’exemple. SITES_RULE est la règle de routage Traefik, et LETSENCRYPT_EMAIL reçoit les alertes de certificat.
ERPNEXT_VERSION=v16.32.1
DB_PASSWORD=<a long random password>
SITES_RULE=Host(`erp.example.com`)
LETSENCRYPT_EMAIL=ops@example.comGénérez maintenant un fichier Compose, puis démarrez-le.
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -dconfig ne démarre rien. Il fusionne le fichier de base avec les fichiers de surcharge et affiche le résultat, après avoir remplacé toutes les variables. Exécutez ensuite le fichier généré. Cette étape supplémentaire est utile : la stack en cours d’exécution tient dans un seul fichier que vous pouvez lire et versionner. Elle ne peut donc pas changer sans que vous le sachiez lorsqu’une personne modifie le fichier env ou lorsque vous mettez à jour le dépôt. fusion de plusieurs fichiers Docker Compose explique en détail les règles de surcharge.
Attendez que db démarre et que configurator se termine, ce qui prend quelques secondes, puis créez le site.
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--install-app erpnext \
--admin-password '<a strong admin password>' \
erp.example.comVérifiez le résultat :
docker compose --project-name erpnext ps
docker compose --project-name erpnext exec backend bench --site erp.example.com list-appslist-apps doit afficher frappe et erpnext avec leurs versions. Un ps sain affiche neuf services à l’état running et aucun à l’état restarting.
Deux problèmes surviennent souvent ici. --mariadb-user-host-login-scope=% est obligatoire avec Docker. Le conteneur de l’application accède à MariaDB via le réseau Docker. Il est donc considéré comme un hôte distant, et un utilisateur de base de données limité à localhost ne peut pas se connecter depuis ce réseau. La création du site échoue alors avec une erreur MariaDB « access denied » qui mentionne l’utilisateur root. La portée % autorise l’utilisateur du nouveau site à se connecter depuis n’importe quel hôte de ce réseau privé.
Le deuxième problème concerne le nom du site. Par défaut, le frontend choisit le site à servir à partir de l’en-tête HTTP Host. Un site créé sous le nom erpnext n’est donc pas accessible à l’adresse erp.example.com, même si les deux existent. Donnez au site le nom du domaine, comme ci-dessus, ou définissez FRAPPE_SITE_NAME_HEADER dans le fichier env avec le nom du site, puis générez à nouveau le fichier Compose.
HTTPS et les conditions requises
Le fichier compose.https.yaml exécute Traefik sur le port 443, redirige le port 80 vers celui-ci et demande des certificats à Let's Encrypt. TLS (transport layer security) protège une facture et un cookie de session contre toute transmission en clair sur le réseau.
Deux conditions doivent être réunies, sinon aucun certificat n’est délivré. L’enregistrement DNS A de erp.example.com doit déjà pointer vers le VPS. Les ports 80 et 443 doivent être accessibles depuis Internet, car Let's Encrypt vérifie que vous contrôlez le nom avec un challenge HTTP-01 sur le port 80. Vérifiez le firewall réseau de votre fournisseur ainsi que celui du serveur. Ce sont deux contrôles distincts, et le firewall du panneau d’administration est souvent oublié.
Les certificats sont enregistrés dans le volume cert-data, à l’emplacement /letsencrypt/acme.json. Si le navigateur affiche un certificat par défaut au lieu du vôtre, recherchez le nom du service proxy dans docker compose --project-name erpnext ps, puis consultez ses journaux pour trouver l’erreur ACME (automatic certificate management environment). Vous exécutez d’autres applications web sur le même serveur ? Une instance Traefik devant plusieurs applications Docker Compose explique comment partager le proxy au lieu d’entrer en conflit pour le port 443. La deuxième application sur un serveur de ce type est souvent destinée aux clients, et un service d’assistance Chatwoot auto-hébergé se place derrière ce même proxy. Les personnes qui gèrent les factures peuvent ainsi répondre aux e-mails et aux conversations des clients au même endroit.
E-mails sortants, sinon les factures ne quittent jamais le serveur
C’est l’étape que la plupart des guides ERPNext ignorent, alors qu’elle détermine si le système est réellement utilisable. Sans e-mails sortants fonctionnels, aucune facture n’atteint un client, aucune réinitialisation de mot de passe n’arrive et aucun rapport planifié n’est envoyé. La stack ne contient pas de serveur de messagerie.
N’essayez pas d’envoyer des e-mails directement depuis le VPS sur le port 25. La plupart des fournisseurs bloquent le port sortant 25 sur les nouveaux comptes. Les messages qui passent sont souvent rejetés ou classés comme spam, car l’adresse d’un VPS récent n’a aucune réputation d’envoi. Utilisez un relay authentifié sur le port 587.
La méthode prise en charge consiste à utiliser l’écran Email Account de l’interface ERPNext, qui stocke le mot de passe sous forme chiffrée. Vous pouvez aussi écrire les clés dans la configuration du site :
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_server smtp.example.com
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_port 587 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config use_tls 1 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_login 'erp@example.com'
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config auto_email_id 'erp@example.com'--parse stocke 587 comme un nombre plutôt que comme la chaîne "587". Relisez le fichier et vérifiez que ces deux valeurs ne sont pas entourées de guillemets :
docker compose --project-name erpnext exec backend \
cat sites/erp.example.com/site_config.jsonDéfinissez mail_password depuis l’écran Email Account plutôt que depuis la ligne de commande. La valeur sera ainsi stockée sous forme chiffrée et n’apparaîtra jamais dans l’historique de votre shell.
Envoyez ensuite un vrai message. Créez une Sales Invoice, envoyez-la par e-mail à une adresse que vous contrôlez et surveillez la file d’attente pendant l’opération :
docker compose --project-name erpnext logs -f queue-shortLes e-mails sortants sont traités par un job en arrière-plan. Un message qui n’arrive jamais apparaît donc généralement comme un job en échec dans ce journal, plutôt que comme une erreur dans le navigateur. Publiez également les enregistrements SPF (sender policy framework) et DKIM (domainkeys identified mail) pour le domaine d’envoi, puis ajoutez une policy DMARC. Sans ces enregistrements, une facture techniquement correcte arrive malgré tout dans le dossier spam du client. Si vous préférez gérer vous-même tout le chemin d’envoi, un serveur de messagerie Mailcow auto-hébergé vous fournit un relay que vous contrôlez, sur un serveur distinct de celui d’ERP.
Des sauvegardes qui se restaurent réellement
Un dump de base de données ne constitue pas à lui seul une sauvegarde d’ERPNext. Les pièces jointes et les fichiers privés se trouvent dans le répertoire sites, pas dans MariaDB. Si vous restaurez uniquement la base de données, chaque bon de commande importé réapparaît sous la forme d’un lien brisé.
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-filesCette commande écrit quatre fichiers dans sites/erp.example.com/private/backups, à l’intérieur du volume sites :
- un dump
-database.sql.gz - une archive
-files.tardes fichiers publics - une archive
-private-files.tardes fichiers privés - une copie
-site_config_backup.jsonde la configuration du site
Le quatrième fichier est celui que l’on supprime souvent, alors que c’est le plus critique. Il contient encryption_key, la clé que Frappe utilise pour chiffrer les mots de passe stockés : identifiants des comptes de messagerie, clés des passerelles de paiement et secrets de toutes les intégrations. Si vous restaurez une base de données sans la clé correspondante, le site se charge normalement, mais l’envoi des e-mails échoue avec :
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.jsonConservez toujours les quatre fichiers ensemble.
Copiez-les ensuite hors du serveur. Une sauvegarde stockée dans le volume ne survit pas à la perte du serveur. De plus, bench la supprime automatiquement : par défaut, il efface de ce répertoire les sauvegardes datant de plus de 24 heures.
docker compose --project-name erpnext cp \
backend:/home/frappe/frappe-bench/sites/erp.example.com/private/backups \
~/erpnext-backupsExécutez cette commande depuis cron, puis envoyez le répertoire vers un emplacement que vous n’administrez pas. Les sauvegardes restic chiffrées sur un stockage distant sont adaptées, car elles chiffrent les données avant l’upload et restic check vérifie que le repository reste lisible. Une sauvegarde ERP est une copie de l’intégralité de votre comptabilité. Elle doit donc être chiffrée au repos sur un matériel qui n’est pas celui-ci.
Testez la restauration avant d’en avoir besoin
Une sauvegarde non testée n’est qu’une hypothèse. Testez-la sur un second site du même serveur, jamais sur le site en production.
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--admin-password '<a strong admin password>' \
restore-test.example.com
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com --force restore \
sites/erp.example.com/private/backups/<stamp>-erp.example.com-database.sql.gz \
--with-public-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-files.tar \
--with-private-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-private-files.tar \
--db-root-password '<your DB_PASSWORD>'Copiez la clé de chiffrement de la configuration sauvegardée vers le site restauré. Sinon, ses intégrations resteront défaillantes :
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com set-config encryption_key '<value from site_config_backup.json>'Vérifiez maintenant la restauration comme le ferait un comptable. Ouvrez le rapport des comptes clients et comparez le solde de clôture avec celui du site en production. Ouvrez une facture d’achat récente et téléchargez sa pièce jointe. Un site qui affiche sa page de connexion ne prouve absolument rien.
Supprimez le site de test lorsque vous avez terminé :
docker compose --project-name erpnext exec backend \
bench drop-site restore-test.example.comPourquoi le pinning des versions est encore plus important pour ERPNext
Sur un site statique, un tag d’image non figé signifie un redémarrage imprévu. Avec ERPNext, cela signifie une migration de schéma. bench migrate réécrit les tables de la base de données et peut réécrire les données des documents. Cette opération est irréversible. Revenir en arrière consiste à restaurer une sauvegarde, pas à effectuer un docker compose down.
Figez donc le tag. ERPNEXT_VERSION=v16.32.1 était la release utilisée dans le propre pwd.yml du dépôt en août 2026. Ne réutilisez pas ce numéro sans le vérifier. Les releases actuelles sont listées sur la page des releases de frappe/erpnext, et les tags d’image disponibles se trouvent sur Docker Hub. Lisez les notes de la version vers laquelle vous migrez avant de lancer la migration.
La mise à niveau commence par une sauvegarde et l’activation du mode maintenance.
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-files
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode onModifiez ERPNEXT_VERSION dans ~/gitops/erpnext.env, puis générez la configuration, téléchargez les images et lancez la migration.
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml pull
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -d
docker compose --project-name erpnext exec backend \
bench --site erp.example.com migrate
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode offLe mode maintenance est important, car migrate modifie le schéma pendant son exécution. Si un utilisateur enregistre un document dans une table partiellement migrée, vous devrez réparer les enregistrements manuellement.
Passez une version majeure à la fois et effectuez une sauvegarde entre chaque étape. Le code de migration d’une release est conçu pour effectuer la mise à niveau depuis la release précédente. Sauter des versions majeures exécute donc des migrations dans une combinaison qui n’a jamais été testée.
Le dépôt fournit également overrides/compose.migrator.yaml, qui ajoute un conteneur exécutant bench --site all migrate à chaque démarrage. C’est pratique. Mais avec un docker compose up dont le tag a changé, cela signifie que votre base de données de production est migrée sans aucune supervision. Sur un système métier, lancez migrate après l’avoir décidé le matin même.
Renforcer la sécurité d’un serveur qui contient des données client
Modifiez le mot de passe Administrator lors de la première connexion. Le fichier Compose d’évaluation fournit admin comme mot de passe, et cette habitude se retrouve en production.
Modifiez DB_PASSWORD pour remplacer la valeur 123 dans example.env. Cette valeur se retrouve en clair dans le ~/gitops/erpnext.yaml généré. Supprimez donc chmod 600 et ne le placez dans aucun dépôt Git. Pour renforcer la sécurité, overrides/compose.mariadb-secrets.yaml lit le mot de passe depuis un fichier de secret Docker plutôt que depuis une variable d’environnement. La section gestion des fichiers env et des secrets dans Docker Compose présente les compromis.
N’exposez que ce qui est nécessaire. Avec la surcharge HTTPS, seuls les ports 80 et 443 sont exposés. N’ajoutez pas de mapping ports au service db pour faciliter la connexion d’un client de base de données : MariaDB serait alors accessible depuis Internet. Utilisez plutôt docker compose --project-name erpnext exec backend bench mariadb. Sur l’hôte, autorisez 22, 80 et 443, refusez le reste et vérifiez également le firewall réseau distinct du fournisseur.
Activez l’authentification à deux facteurs dans System Settings pour chaque compte possédant le rôle System Manager. Ce rôle peut lire tous les documents et exporter toutes les tables. Traitez-le donc comme un compte administrateur, et non comme une simple commodité. Si vous exécutez plusieurs applications auto-hébergées, Authentik comme fournisseur d’authentification unique auto-hébergé est préférable à l’ajout d’un mot de passe par application.
Appliquez les mises à jour de l’hôte et redémarrez-le pour installer les mises à jour du kernel. Avant de compter sur le redémarrage automatique de la stack, vérifiez dans le fichier généré la présence d’une policy restart pour chaque service. Sans cette policy, la stack reste arrêtée après le redémarrage. La section faire redémarrer une stack Docker Compose après un redémarrage couvre la configuration systemd.
Quand ERPNext devient trop limité pour un seul VPS
Un seul VPS peut héberger une petite entreprise pendant longtemps. Voici les signes qu’il atteint ses limites :
- Les tâches en arrière-plan s’accumulent. Les e-mails et les importations arrivent donc avec plusieurs minutes ou plusieurs heures de retard.
docker inspectsignale des conteneurs avec"OOMKilled": trueou le code de sortie 137.- Les rapports qui s’exécutaient en deux secondes prennent trente secondes, et MariaDB est le processus qui consomme le CPU.
- Les sauvegardes durent suffisamment longtemps pour qu’une exécution chevauche la suivante.
Commencez par attribuer à MariaDB des ressources qu’elle ne partage pas, car la base de données et les workers Python se disputent la même mémoire, et le buffer pool est le composant qui en demande le plus. Un application server plus puissant apporte moins de gains que prévu. exécuter la base de données dans Docker ou sur l’hôte traite cette décision, et définir des limites de mémoire dans Docker Compose empêche un conteneur d’affamer les autres pendant cette transition.
Ensuite, ajoutez des queue workers plutôt que de la capacité web. Les tâches lentes d’ERPNext s’exécutent en arrière-plan : génération de rapports et importations en masse. Ajouter plusieurs conteneurs de workers coûte moins cher qu’un serveur plus puissant et corrige le problème dont les utilisateurs se plaignent réellement.
FAQ
De combien de RAM ERPNext a-t-il besoin sur un VPS ?
La documentation publiée recommande au minimum 4 GB avec 2 vCPU, mais cette configuration est réservée à l’évaluation. Pour une entreprise qui l’utilise quotidiennement, prévoyez 8 GB, 4 vCPU et 100 GB de SSD. En dessous, l’out-of-memory killer du kernel arrête les conteneurs sous charge, ce que docker inspect signale comme "OOMKilled": true avec le code de sortie 137. Il s’agit de points de départ, pas de mesures. Surveillez donc votre consommation mémoire pendant le premier mois.
Puis-je exécuter pwd.yml en production ?
Non. Le README du projet indique que ce fichier est destiné uniquement aux évaluations de courte durée et précise qu’il n’est pas possible d’y installer des applications personnalisées. Utilisez compose.yaml avec les overrides MariaDB, Redis et HTTPS, générez un fichier unique avec docker compose config, puis exécutez ce fichier.
Pourquoi mon site ERPNext est-il inaccessible juste après sa création ?
Par défaut, le frontend choisit le site à servir à partir de l’en-tête HTTP Host. Le nom du site doit donc correspondre au domaine saisi dans le navigateur. Un site créé sous le nom erpnext n’est pas servi à l’adresse erp.example.com. Créez le site en utilisant le domaine comme nom, ou définissez FRAPPE_SITE_NAME_HEADER dans le fichier env avec le nom du site, générez à nouveau le fichier compose, puis redémarrez la stack.
Que doit contenir une sauvegarde ERPNext ?
Quatre fichiers à conserver ensemble : le dump -database.sql.gz, les archives -files.tar et -private-files.tar, ainsi que la copie de configuration -site_config_backup.json. L’exécution de bench --site erp.example.com backup --with-files produit ces quatre fichiers. La copie de configuration contient encryption_key. Sans elle, une restauration ne peut pas déchiffrer les mots de passe d’intégration enregistrés, ce qui se manifeste par Encryption key is invalid! Please check site_config.json.
Comment mettre à niveau ERPNext sans endommager mes données ?
Effectuez une sauvegarde avec --with-files, activez le mode maintenance, modifiez ERPNEXT_VERSION dans votre fichier env, générez à nouveau le fichier compose, récupérez les nouvelles images, démarrez la stack, puis exécutez bench --site erp.example.com migrate et désactivez le mode maintenance. Avancez d’une version majeure à la fois et lisez d’abord les notes de version, car migrate réécrit le schéma et les données des documents sans possibilité d’annulation. Pour revenir en arrière, restaurez la sauvegarde créée au début.