Installer ERPNext sur un VPS avec Docker
Déployez ERPNext avec Docker sur votre VPS : 11 conteneurs, dimensionnement, TLS, e-mails sortants, versions figées et restauration réellement testée.
Ce que vous vous engagez à exploiter
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 données 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 verrouillé n’est qu’une migration de schéma en attente.
Quelques 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, pour 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 section 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. Ces valeurs sont des points de départ, pas des mesures effectuées dans ce guide. Le volume de vos propres documents détermine la valeur réelle. 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 le principal problème.
Soyez réaliste avec les petites offres. Un VPS de 1 Go ou 2 Go démarre la stack, puis tombe en panne dès le premier import ou le premier rapport long. Neuf conteneurs à longue durée d’exécution, le buffer pool de MariaDB et un worker Python qui génère un rapport ne tiennent pas dans cette quantité de mémoire. L’échec n’est pas géré proprement. L’out of memory killer du kernel arrête un conteneur, et docker inspect affiche alors "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 effectué.
Pour une entreprise qui utilise ERPNext tous les jours, 8 Go de RAM, 4 vCPU et 100 Go de SSD constituent le minimum réaliste. La RAM vient à manquer 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, configurator et create-site, exécutent leur tâche une seule 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 se trouvebench.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.schedulerexécute les tâches planifiées, notamment les rapports programmés et les documents à répétition automatique.websocketest le processus socket.io utilisé pour 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 de tâches.
Cette séparation est importante à comprendre, car elle indique quel journal consulter. Un e-mail bloqué relève d’un problème de 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 signale un problème de websocket. Consulter les journaux de backend dans l’un ou l’autre cas vous ferait perdre un après-midi.
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 destinée uniquement à une évaluation de courte durée. Vous ne pourrez pas installer d’applications personnalisées avec cette configuration. » Utilisez-la pour examiner ERPNext pendant un après-midi. Ne l’utilisez pas pour faire fonctionner l’informatique 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 avertissements liés aux certificats.
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 surcharges et affiche le résultat après substitution de 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 récupérez une nouvelle version du dépôt. fonctionnement de la 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 leur version. Un ps en bon état 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. MariaDB le considère donc comme un hôte distant, et un utilisateur de base de données limité à localhost ne peut pas s’y connecter. La création du site échoue alors avec une erreur d’accès refusé de MariaDB 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 second problème concerne le nom du site. Par défaut, le frontend sélectionne 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 de nouveau le fichier Compose.
HTTPS et les conditions préalables
L’override 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) empêche qu’une facture ou un cookie de session circule en clair sur le réseau.
Deux conditions doivent être remplies, 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 au moyen d’un challenge HTTP-01 sur le port 80. Vérifiez le firewall réseau de votre fournisseur ainsi que celui du serveur. Il s’agit de deux contrôles distincts, et le firewall du panel est souvent oublié.
Les certificats sont stockés dans le volume cert-data, à /letsencrypt/acme.json. Si le navigateur affiche un certificat par défaut au lieu du vôtre, trouvez le nom du service proxy dans docker compose --project-name erpnext ps et consultez ses logs pour identifier 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 de faire entrer les services en conflit sur le port 443.
E-mails sortants, sinon les factures ne quittent jamais le serveur
C’est l’étape que la plupart des guides ERPNext omettent, alors qu’elle détermine si le système est réellement utile. Sans e-mails sortants fonctionnels, aucune facture n’atteint un client, aucun e-mail de réinitialisation de mot de passe n’arrive et aucun rapport planifié n’est envoyé. La stack ne contient aucun mail server.
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 neuf 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 dans l’interface ERPNext. Le mot de passe y est stocké de manière chiffrée. Vous pouvez aussi inscrire 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 et non 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 sur la ligne de commande. La valeur sera ainsi stockée de manière 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 queue 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 log, 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 politique DMARC. Sans ces enregistrements, une facture techniquement correcte arrive tout de même dans le dossier spam du client. Si vous préférez gérer vous-même l’ensemble du parcours, un mail server 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 cassé.
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 sa perte pose le plus de problèmes. Il contient encryption_key, la clé que Frappe utilise pour chiffrer les mots de passe stockés : identifiants des comptes e-mail, clés des payment gateways et tous les secrets d’intégration. Si vous restaurez une base de données sans la clé correspondante, le site se charge normalement, mais l’envoi d’e-mails échoue avec :
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.jsonConservez toujours les quatre fichiers ensemble.
Transférez-les ensuite hors du serveur. Une sauvegarde stockée dans le volume ne survit pas à la perte du serveur. De plus, bench la supprime lors du nettoyage : 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 vers un stockage distant sont adaptées, car elles chiffrent les données avant l’envoi et restic check vérifie que le repository reste lisible. Une sauvegarde ERP est une copie de l’intégralité de votre ledger. Elle doit donc être chiffrée au repos et stockée sur un matériel différent de celui-ci.
Tester la restauration avant d’en avoir besoin
Une sauvegarde non testée repose sur des suppositions. 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 depuis la configuration sauvegardée vers le site restauré. Sinon, ses intégrations ne fonctionneront plus :
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 ensuite la restauration comme le ferait un comptable. Ouvrez le rapport Accounts Receivable 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 rien.
Supprimez le site de test une fois terminé :
docker compose --project-name erpnext exec backend \
bench drop-site restore-test.example.comPourquoi le pinning de version est encore plus important pour ERPNext
Sur un site statique, un tag d’image non épinglé signifie un redémarrage inattendu. 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 nécessite une restauration depuis une sauvegarde, pas un docker compose down.
Épinglez donc le tag. ERPNEXT_VERSION=v16.32.1 était la release épinglée dans le pwd.yml du dépôt lui-même 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 cible avant de lancer la mise à niveau.
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 exécutez 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 soumet un document alors qu’une table n’a été que partiellement migrée, vous devrez réparer les enregistrements manuellement.
Passez d’une version majeure à la suivante, avec 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 que personne n’a 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 un docker compose up avec un tag modifié peut aussi migrer votre base de données de production sans aucune surveillance. Sur un système métier, exécutez migrate uniquement après l’avoir décidé le matin même.
Renforcer la sécurité d’un serveur qui contient des données clients
Modifiez le mot de passe de l’Administrator lors de la première connexion. Le fichier Compose d’évaluation fournit admin comme mot de passe, et cette habitude peut se retrouver en production.
Remplacez DB_PASSWORD par une valeur différente de 123 dans example.env. Cette valeur apparaît en clair dans ~/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 dans un fichier de Docker secret plutôt que dans une variable d’environnement. gérer les fichiers d’environnement et les secrets dans Docker Compose présente les compromis entre ces méthodes.
N’exposez que les ports nécessaires. Avec l’override 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 qui possède 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 SSO auto-hébergé est préférable à l’ajout d’un mot de passe par application.
Mettez à jour l’hôte et redémarrez-le pour appliquer 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. faire redémarrer une stack Docker Compose après un redémarrage explique la configuration systemd.
Quand ERPNext devient trop limité sur un seul VPS
Un seul VPS peut héberger une petite entreprise pendant longtemps. Voici les signes qu’il ne suffit plus :
- Les tâches d’arrière-plan s’accumulent. Les e-mails et les imports arrivent alors avec plusieurs minutes ou plusieurs heures de retard.
docker inspectsignale des conteneurs avec"OOMKilled": trueou le code de sortie 137.- Les rapports qui prenaient deux secondes en prennent trente. MariaDB est alors le processus qui consomme le CPU.
- Les sauvegardes durent assez longtemps pour qu’une exécution chevauche la suivante.
Commencez par donner à MariaDB des ressources qu’il ne partage pas. La base de données et les workers Python se disputent la même mémoire, et le buffer pool est l’élément qui en demande le plus. Un serveur applicatif plus puissant apporte moins de gains que prévu. exécuter la base de données dans Docker ou sur l’hôte présente ce choix, et définir les limites mémoire dans Docker Compose évite qu’un conteneur prive les autres de ressources pendant cette transition.
Ajoutez ensuite des queue workers plutôt que d’augmenter la capacité web. Les tâches lentes d’ERPNext s’exécutent en arrière-plan : génération de rapports et imports en masse. Ajouter des 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 recommande au minimum 4 Go avec 2 vCPU, mais cette configuration est réservée à l’évaluation. Pour une entreprise qui l’utilise quotidiennement, prévoyez 8 Go, 4 vCPU et 100 Go de SSD. En dessous, l’out-of-memory killer du kernel arrête les conteneurs sous charge, ce que docker inspect signale avec "OOMKilled": true et le code de sortie 137. Ces valeurs sont des points de départ, pas des 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 uniquement prévu pour une évaluation de courte durée et précise qu’il ne permet pas d’y installer des applications personnalisées. Utilisez compose.yaml avec les overrides MariaDB, Redis et HTTPS, regroupez-les dans un seul fichier 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 utilisé 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 de 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 de nouveau le fichier Compose, récupérez les images, démarrez la stack, puis exécutez bench --site erp.example.com migrate et désactivez le mode maintenance. Ne franchissez qu’une version majeure à la fois et consultez 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.