SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-28

ERPNext zelf hosten met Docker op een VPS

Leer hoe u ERPNext succesvol draait op een eigen VPS met Docker. Deze gids behandelt de elf containers, TLS-configuratie, e-mailinstellingen en het testen van uw back-ups.

Waar u zich voor aanmeldt

Het zelf hosten van ERPNext op een VPS is een operationele taak, geen installatie met één commando. De officiële Docker Compose-stack bestaat uit elf containers en bevat uw grootboek en klantgegevens. Dit verhoogt de standaard voor alles wat hieronder volgt: een back-up is pas een back-up als u deze succesvol heeft teruggezet, en een niet-gefixeerde image-tag is een schema-migratie die vroeg of laat problemen veroorzaakt.

Een aantal namen komt regelmatig terug. ERPNext is de bedrijfsapplicatie. Frappe is het Python-framework dat daaronder ligt. Bench is de command-line tool die sites beheert en die al in de containers is geïnstalleerd. Een site is één tenant: één MariaDB-database plus één map met geüploade bestanden. Bijna elk commando hier wordt uitgevoerd met bench binnen de backend-container voor één specifieke site.

Deze handleiding gebruikt de frappe_docker-repository, de deployment die door het project wordt onderhouden. Elk onderstaand commando is in augustus 2026 gecontroleerd aan de hand van die repository. Als Docker Compose nieuw voor u is, behandelt het draaien van Docker Compose op een VPS de basiskennis waar deze handleiding vanuit gaat.

Hoeveel VPS-capaciteit heeft ERPNext nodig?

ChartCommon published ERPNext sizing tiers (guidance, not a measurement)
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
  }
]

De gepubliceerde richtlijnen beginnen bij 2 vCPU en 4 GB RAM voordat een enkele gebruiker inlogt. Dit is het niveau voor evaluatie. Dit zijn startpunten, geen metingen uit deze handleiding, en het werkelijke aantal wordt bepaald door uw eigen documentvolume. De laatste rij is geen officieel minimum. Het is ongeveer het punt waarop geheugengebruik geen zorgpunt meer is.

Wees realistisch over de kleine abonnementen. Een VPS met 1 GB of 2 GB RAM zal de stack opstarten, maar vervolgens crashen bij de eerste import of het eerste uitgebreide rapport. Negen langlopende containers, de bufferpool van MariaDB en een Python-worker die een rapport genereert, passen simpelweg niet in dat geheugen. Het falen verloopt niet gecontroleerd. De out-of-memory killer van de kernel stopt een container, en docker inspect toont vervolgens "OOMKilled": true met exitcode 137. Een worker die halverwege een taak wordt beëindigd, laat een ingediend document achter met onvoltooid achtergrondwerk.

Voor een bedrijf dat dagelijks ERPNext gebruikt, is 8 GB RAM en 4 vCPU met 100 GB SSD de eerlijke ondergrens. RAM raakt als eerste op. De schijfruimte groeit sneller dan verwacht, omdat elke bijlage en elke lokale back-up op hetzelfde volume terechtkomt als de database.

De elf containers en hun functies

Voer docker compose ps uit nadat de stack actief is en er negen containers draaien. Twee andere, configurator en create-site, voeren hun taak eenmalig uit en stoppen daarna; vandaar het totaal van elf.

  • backend draait de Frappe-applicatie onder gunicorn. Hier bevindt zich bench.
  • frontend is nginx. Deze serveert statische assets en stuurt al het overige verkeer door naar de backend.
  • queue-short en queue-long zijn RQ (Redis Queue) workers. Zij verwerken achtergrondtaken zoals uitgaande e-mail, imports en het genereren van rapporten.
  • scheduler activeert de tijdsgebonden taken, waaronder geplande rapporten en documenten met automatische herhaling.
  • websocket is het socket.io-proces dat live-updates in de browser mogelijk maakt.
  • db is MariaDB.
  • redis-cache en redis-queue zijn twee afzonderlijke Redis-instanties: één voor cache en één voor de takenwachtrij.

Het is nuttig om dit onderscheid te kennen, omdat het aangeeft welk logbestand u moet raadplegen. Een vastgelopen e-mail is een probleem met de wachtrij-worker, dus docker compose logs -f queue-short is het juiste commando. Een pagina die wel laadt maar de notificatiebadge niet bijwerkt, duidt op een websocket-probleem. Het lezen van de backend-logs voor een van deze problemen is tijdverspilling.

Installeren met de productie-compose-bestanden, niet de demo

De repository levert pwd.yml, en de README is hierover duidelijk: "Deze opzet is uitsluitend bedoeld voor evaluatie op korte termijn. U kunt in deze opzet geen aangepaste apps installeren." Gebruik dit om ERPNext een middag te bekijken. Draai er geen bedrijf op.

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.env

Open ~/gitops/erpnext.env en wijzig vier waarden. ERPNEXT_VERSION zet de image-tag vast. DB_PASSWORD wordt in het voorbeeldbestand geleverd als 123. SITES_RULE is de Traefik-routeringsregel, en LETSENCRYPT_EMAIL ontvangt certificaatwaarschuwingen.

ERPNEXT_VERSION=v16.32.1
DB_PASSWORD=<a long random password>
SITES_RULE=Host(`erp.example.com`)
LETSENCRYPT_EMAIL=ops@example.com

Render nu één compose-bestand en start het vervolgens.

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 -d

config start niets. Het voegt het basisbestand samen met de overrides en print het resultaat waarbij elke variabele al is ingevuld. U voert vervolgens dat gerenderde bestand uit. Deze extra stap is nuttig: de draaiende stack is één bestand dat u kunt lezen en committen, waardoor het niet onverwacht kan veranderen wanneer iemand het env-bestand bewerkt of wanneer u de repository ophaalt. hoe meerdere Docker Compose-bestanden samensmelten legt de override-regels in detail uit.

Wacht tot db is gestart en configurator is afgesloten, wat enkele seconden duurt, en maak vervolgens de site aan.

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.com

Controleer het resultaat:

docker compose --project-name erpnext ps
docker compose --project-name erpnext exec backend bench --site erp.example.com list-apps

list-apps hoort frappe en erpnext met hun versies te printen. Een gezonde ps toont negen services in de status running en geen in restarting.

Twee zaken gaan hier vaak mis. --mariadb-user-host-login-scope=% is niet optioneel onder Docker. De app-container bereikt MariaDB via het Docker-netwerk, waardoor deze aankomt als een externe host, en een databasegebruiker die beperkt is tot localhost kan vanaf daar niet inloggen. Het aanmaken van de site mislukt dan met een MariaDB access denied-foutmelding voor de gebruiker root. Het %-bereik verleent de gebruiker van de nieuwe site toegang vanaf elke host op dat privénetwerk.

Het tweede punt is de sitenaam. De frontend kiest standaard welke site moet worden geserveerd op basis van de HTTP Host-header, dus een site die is aangemaakt als erpnext is niet bereikbaar via erp.example.com, ook al bestaan beide. Geef de site de naam van het domein, zoals hierboven, of stel FRAPPE_SITE_NAME_HEADER in het env-bestand in op de sitenaam en render het compose-bestand opnieuw.

HTTPS, en de voorwaarden voor een correcte werking

De compose.https.yaml override voert Traefik uit op poort 443, leidt poort 80 hiernaartoe om en vraagt certificaten aan bij Let's Encrypt. TLS (transport layer security) zorgt ervoor dat facturen en sessiecookies niet in platte tekst over het netwerk worden verzonden.

Er moet aan twee voorwaarden worden voldaan, anders wordt er geen certificaat uitgegeven. Het DNS A-record voor erp.example.com moet al naar de VPS wijzen. Poorten 80 en 443 moeten bereikbaar zijn vanaf het internet, omdat Let's Encrypt controleert of u de domeinnaam beheert via een HTTP-01 challenge op poort 80. Controleer zowel de netwerkfirewall van uw provider als de firewall op de server zelf. Dit zijn afzonderlijke instellingen, en de firewall in het configuratiepaneel wordt vaak vergeten.

Certificaten worden opgeslagen in het cert-data volume op /letsencrypt/acme.json. Als de browser een standaardcertificaat toont in plaats van het uwe, zoek dan de naam van de proxyservice in docker compose --project-name erpnext ps en lees de logs voor de ACME (automatic certificate management environment) foutmelding. Draait u andere webapplicaties op dezelfde server? één Traefik-instantie voor meerdere Docker Compose-applicaties laat zien hoe u de proxy deelt in plaats van te strijden om poort 443. De tweede applicatie op een dergelijke server is vaak klantgericht, en een zelfgehoste Chatwoot support desk bevindt zich achter diezelfde proxy, zodat de medewerkers die facturen verwerken ook klantvragen via e-mail en chat op één plek kunnen beantwoorden.

Uitgaande e-mail, of waarom facturen de server niet verlaten

Dit is de stap die de meeste ERPNext-handleidingen overslaan, terwijl deze bepaalt of het systeem bruikbaar is. Zonder werkende uitgaande e-mail bereikt geen enkele factuur een klant, komt er geen wachtwoordreset aan en wordt er geen gepland rapport verstuurd. De stack bevat geen mailserver.

Probeer geen e-mail rechtstreeks vanaf de VPS te versturen via poort 25. De meeste providers blokkeren uitgaand verkeer op poort 25 voor nieuwe accounts, en alles wat wel wordt verstuurd, wordt geweigerd of als spam gemarkeerd omdat een nieuw VPS-adres geen reputatie als verzender heeft. Gebruik een geauthenticeerde relay op poort 587.

De ondersteunde methode is het scherm Email Account in de ERPNext-interface, waar het wachtwoord versleuteld wordt opgeslagen. U kunt de sleutels ook in de site-configuratie schrijven:

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 slaat 587 op als een getal in plaats van als de string "587". Lees het bestand uit en controleer of deze twee waarden niet tussen aanhalingstekens staan:

docker compose --project-name erpnext exec backend \
  cat sites/erp.example.com/site_config.json

Stel mail_password in via het scherm Email Account in plaats van via de command line, zodat het versleuteld wordt opgeslagen en niet in uw shell-geschiedenis terechtkomt.

Verstuur vervolgens een echt bericht. Maak een Sales Invoice aan, e-mail deze naar een adres dat u beheert en monitor de wachtrij terwijl u dit doet:

docker compose --project-name erpnext logs -f queue-short

Uitgaande e-mail is een achtergrondtaak; een bericht dat niet aankomt, verschijnt meestal als een mislukte taak in dat logboek in plaats van als een foutmelding in de browser. Publiceer SPF (Sender Policy Framework) en DKIM (DomainKeys Identified Mail) records voor het verzendende domein en voeg daarna een DMARC-beleid toe. Zonder deze records belandt een technisch correcte factuur alsnog in de spammap van de klant. Als u liever het volledige traject in eigen beheer heeft, biedt een zelfgehoste Mailcow-mailserver u een relay die u zelf beheert, op een aparte server los van de ERP.

Backups die daadwerkelijk herstellen

Een database-dump op zichzelf is geen back-up van ERPNext. Bijlagen en privébestanden bevinden zich in de sites-directory, niet in MariaDB. Als u alleen de database herstelt, verschijnt elke geüploade inkooporder als een kapotte link.

docker compose --project-name erpnext exec backend \
  bench --site erp.example.com backup --with-files

Dit schrijft vier bestanden naar sites/erp.example.com/private/backups binnen het sites volume:

  • een -database.sql.gz dump
  • een -files.tar archief van publieke bestanden
  • een -private-files.tar archief van privébestanden
  • een -site_config_backup.json kopie van de site-configuratie

Het vierde bestand is het bestand dat mensen vaak weggooien, en dat is precies wat voor problemen zorgt. Het bevat encryption_key, de sleutel die Frappe gebruikt om opgeslagen wachtwoorden te versleutelen: inloggegevens voor e-mailaccounts, sleutels voor betaalgateways en elk integratiegeheim. Als u een database herstelt zonder de bijbehorende sleutel, laadt de site normaal, maar mislukt het verzenden van e-mail met de volgende foutmelding:

frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.json

Houd deze vier bestanden altijd bij elkaar.

Verplaats ze vervolgens van de server af. Een back-up binnen het volume overleeft de server niet, en bench verwijdert deze bovendien: standaard worden back-ups die ouder zijn dan 24 uur uit die directory verwijderd.

docker compose --project-name erpnext cp \
  backend:/home/frappe/frappe-bench/sites/erp.example.com/private/backups \
  ~/erpnext-backups

Voer dit uit via cron en verplaats de directory vervolgens naar een locatie die u niet zelf beheert. versleutelde restic-back-ups naar externe opslag is hiervoor het juiste hulpmiddel, omdat het de gegevens versleutelt vóór de upload en restic check bewijst dat de repository nog steeds leesbaar is. Een ERP-back-up is een kopie van uw volledige grootboek; deze hoort daarom versleuteld te zijn op hardware die losstaat van de huidige server.

Test de restore voordat u deze nodig heeft

Een back-up die niet is getest, is slechts een aanname. Test de restore op een tweede locatie op dezelfde server; overschrijf nooit de actieve omgeving.

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>'

Kopieer de encryptiesleutel uit de back-upconfiguratie naar de herstelde site, anders blijven de integraties defect:

docker compose --project-name erpnext exec backend \
  bench --site restore-test.example.com set-config encryption_key '<value from site_config_backup.json>'

Controleer de restore nu op de wijze van een accountant. Open het rapport voor debiteuren en vergelijk het eindsaldo met de actieve site. Open een recente inkoopfactuur en download de bijlage. Een site die enkel de inlogpagina laadt, bewijst niets.

Verwijder de testsite zodra u klaar bent:

docker compose --project-name erpnext exec backend \
  bench drop-site restore-test.example.com

Waarom versie-pinning cruciaal is voor ERPNext

Bij een statische website betekent een niet-gepinde image-tag een verrassende herstart. Bij ERPNext betekent het een schema-migratie. bench migrate herschrijft databasetabellen en kan documentgegevens wijzigen; er is geen ongedaan-maakfunctie. Terugdraaien betekent een herstel vanaf een backup, niet een docker compose down.

Pin daarom de tag. ERPNEXT_VERSION=v16.32.1 was de release die in augustus 2026 in de eigen pwd.yml van de repository was gepind. Neem dat nummer niet zomaar over zonder het te controleren. Huidige releases staan vermeld op de frappe/erpnext releases pagina, en de beschikbare image-tags staan op Docker Hub. Lees de opmerkingen voor de versie waarnaar u overstapt voordat u de migratie uitvoert.

De upgrade zelf begint met een backup en de onderhoudsmodus.

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 on

Bewerk ERPNEXT_VERSION in ~/gitops/erpnext.env, en voer daarna de render, pull en migratie uit.

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 off

De onderhoudsmodus is van belang omdat migrate het schema wijzigt terwijl het proces draait. Een gebruiker die een document indient terwijl een tabel half gemigreerd is, leidt ertoe dat u handmatig records moet herstellen.

Migreer één major-versie per keer, met een backup tussen elke stap. De migratiecode in een release is geschreven om te upgraden vanaf de voorgaande release; het overslaan van major-versies voert migraties uit in een combinatie die door niemand is getest.

De repository levert ook overrides/compose.migrator.yaml, wat een container toevoegt die bij elke start bench --site all migrate uitvoert. Dit is handig. Het betekent echter ook dat een docker compose up met een gewijzigde tag uw productiedatabase migreert zonder toezicht. Voer bij een zakelijk systeem een migratie uit als een bewuste beslissing die u die ochtend heeft genomen.

Het beveiligen van een server met klantgegevens

Wijzig het Administrator-wachtwoord bij de eerste aanmelding. Het evaluatie-compose-bestand levert admin als wachtwoord, en deze gewoonte wordt vaak onbedoeld overgenomen in productieomgevingen.

Wijzig DB_PASSWORD en vervang de 123 in example.env. Deze waarde komt in platte tekst terecht in het gegenereerde ~/gitops/erpnext.yaml, dus chmod 600 het bestand en houd het buiten elk git-repository. Voor een robuustere oplossing leest overrides/compose.mariadb-secrets.yaml het wachtwoord uit een Docker-secret-bestand in plaats van uit een omgevingsvariabele. omgaan met env-bestanden en secrets in Docker Compose behandelt de afwegingen.

Stel alleen de noodzakelijke poorten open. Met de HTTPS-override zijn poort 80 en 443 de enige die worden blootgesteld. Voeg geen ports-mapping toe aan de db-service om een database-client makkelijker te laten verbinden: dit plaatst MariaDB direct op het openbare internet. Gebruik in plaats daarvan docker compose --project-name erpnext exec backend bench mariadb. Sta op de host alleen poort 22, 80 en 443 toe, blokkeer de rest en controleer ook de externe netwerkfirewall van de provider.

Schakel tweefactorauthenticatie in via de Systeeminstellingen voor elk account met de rol System Manager. Deze rol kan elk document inzien en elke tabel exporteren; behandel dit account daarom als een administrator-account en niet als een gemakkelijke toegang. Als u meerdere zelfgehoste applicaties beheert, is Authentik als zelfgehoste single sign-on provider een betere keuze dan het beheren van een extra wachtwoord per applicatie.

Installeer updates op de host en voer een herstart uit voor kernel-updates. Controleer voordat u vertrouwt op het automatisch opstarten van de stack of het gegenereerde bestand een restart-beleid bevat voor elke service, omdat een stack zonder dit beleid na een herstart uitgeschakeld blijft. een Docker Compose-stack laten herstarten na een reboot behandelt de kant van systemd.

Wanneer ERPNext niet langer soepel draait op één VPS

Eén VPS kan een klein bedrijf lange tijd ondersteunen. De signalen dat dit niet langer volstaat:

  • Achtergrondtaken hopen zich op, waardoor e-mails en imports minuten of uren te laat aankomen.
  • docker inspect rapporteert containers met "OOMKilled": true of exitcode 137.
  • Rapporten die twee seconden duurden, nemen nu dertig seconden in beslag, waarbij MariaDB het proces is dat de CPU bezet houdt.
  • Back-ups duren zo lang dat de ene back-up de volgende geplande taak overlapt.

Begin met het toewijzen van resources aan MariaDB die niet gedeeld worden, omdat de database en de Python-workers strijden om hetzelfde geheugen en de buffer pool het onderdeel is dat hier het meeste van nodig heeft. Een grotere applicatieserver helpt minder dan men verwacht. het draaien van de database in Docker of op de host behandelt die beslissing, en het instellen van geheugenlimieten in Docker Compose voorkomt dat één container de andere uithongert terwijl u dit regelt.

Voeg daarna queue workers toe in plaats van webcapaciteit. Het trage werk in ERPNext is achtergrondwerk: het genereren van rapporten en bulk-imports. Meer worker-containers kosten minder dan een grotere server, en ze verhelpen het symptoom waar gebruikers daadwerkelijk over klagen.

FAQ

Hoeveel RAM heeft ERPNext nodig op een VPS?

De gepubliceerde richtlijnen beginnen bij 4 GB met 2 vCPU, en dit niveau is uitsluitend bedoeld voor evaluatiedoeleinden. Voor een bedrijf dat het dagelijks gebruikt, dient u rekening te houden met 8 GB en 4 vCPU met 100 GB SSD-opslag. Daaronder zal de kernel out of memory killer containers onder belasting beëindigen, wat docker inspect rapporteert als "OOMKilled": true met exitcode 137. Dit zijn startpunten in plaats van definitieve metingen; monitor daarom uw eigen geheugengebruik gedurende de eerste maand.

Kan ik pwd.yml in productie draaien?

Nee. De README van het project beschrijft dit bestand als uitsluitend bedoeld voor kortstondige evaluatie en vermeldt dat u er geen aangepaste apps in kunt installeren. Gebruik compose.yaml met de MariaDB-, Redis- en HTTPS-overrides, voeg deze samen tot één bestand met docker compose config en voer dat bestand uit.

Waarom is mijn ERPNext-site direct na creatie onbereikbaar?

De frontend kiest standaard welke site moet worden geserveerd op basis van de HTTP Host-header; de sitenaam moet dus overeenkomen met het domein in de browser. Een site die is aangemaakt als erpnext wordt niet geserveerd op erp.example.com. Maak de site aan met het domein als naam, of stel FRAPPE_SITE_NAME_HEADER in het env-bestand in op de sitenaam, render het compose-bestand opnieuw en herstart de stack.

Wat moet er in een ERPNext-back-up staan?

Vier bestanden die bij elkaar worden bewaard: de -database.sql.gz-dump, de -files.tar- en -private-files.tar-archieven, en de -site_config_backup.json-configuratiekopie. Het uitvoeren van bench --site erp.example.com backup --with-files genereert alle vier de bestanden. De configuratiekopie bevat encryption_key; een herstel zonder dit bestand maakt opgeslagen integratiewachtwoorden onleesbaar, wat zich uit als Encryption key is invalid! Please check site_config.json.

Hoe upgrade ik ERPNext zonder mijn data te beschadigen?

Maak een back-up met --with-files, schakel de onderhoudsmodus in, wijzig ERPNEXT_VERSION in uw env-bestand, render het compose-bestand opnieuw, voer een pull uit, start de stack, voer vervolgens bench --site erp.example.com migrate uit en schakel de onderhoudsmodus weer uit. Upgrade één hoofdversie per keer en lees eerst de release notes, omdat migrate het schema en de documentdata herschrijft zonder mogelijkheid tot ongedaan maken. Terugdraaien betekent het herstellen van de back-up die u aan het begin heeft gemaakt.