SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

ERPNext zelf hosten op een VPS met Docker

Leer hoe u ERPNext succesvol draait op uw eigen VPS. Deze gids behandelt de elf containers, versiebeheer, 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 enkel 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 eronder 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 bench binnen de backend-container voor één specifieke site.

Deze handleiding gebruikt de frappe_docker-repository, de implementatie die door het project zelf 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 evaluatiedoeleinden. Dit zijn startpunten, geen metingen uit deze handleiding, en uw eigen documentvolume bepaalt de werkelijke benodigde capaciteit. De laatste rij is geenszins een gepubliceerd 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 ERPNext dagelijks gebruikt, is 8 GB RAM en 4 vCPU met 100 GB SSD de eerlijke ondergrens. Het RAM-geheugen 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 is opgestart en negen containers actief zijn. 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 voeren achtergrondtaken uit zoals het versturen van e-mail, imports en het genereren van rapporten.
  • scheduler start de tijdsgebonden taken, waaronder geplande rapporten en documenten met automatische herhaling.
  • websocket is het socket.io-proces dat live-updates in de browser verzorgt.
  • db is MariaDB.
  • redis-cache en redis-queue zijn twee afzonderlijke Redis-instanties: één voor cache en één voor de wachtrij van taken.

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

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

De repository levert pwd.yml mee en de README is hierover duidelijk: "Deze opstelling is uitsluitend bedoeld voor evaluatie op korte termijn. U kunt in deze opstelling 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

Genereer nu één compose-bestand en start dit 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 alle variabelen al zijn ingevuld. U voert vervolgens dat gegenereerde 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 daarna 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 tonen. Een gezonde ps toont negen services in de status running en geen in de status restarting.

Twee zaken gaan hier vaak mis. --mariadb-user-host-login-scope=% is niet optioneel binnen Docker. De app-container bereikt MariaDB via het Docker-netwerk, waardoor deze als een externe host binnenkomt; 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 root-gebruiker. 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 genereer 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 verstuurd.

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 via een HTTP-01 challenge op poort 80 controleert of u de domeinnaam beheert. 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.

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

Dit is de stap die de meeste ERPNext-handleidingen overslaan, en het is de stap die 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 afzender 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 die 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 bekijk 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 daarom meestal als een mislukte taak in dat logbestand in plaats van als een foutmelding in de browser. Publiceer ook SPF (Sender Policy Framework) en DKIM (DomainKeys Identified Mail) records voor het domein van waaruit u verstuurt, 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 de volledige keten 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 alleen is geen backup van ERPNext. Bijlagen en privébestanden bevinden zich in de directory sites, 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 het bestand dat 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

Bewaar deze vier bestanden altijd samen.

Verplaats ze vervolgens van de server. Een backup binnen het volume overleeft de server niet, en bench verwijdert deze bovendien: standaard worden backups 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 backups naar off-site opslag is hiervoor de juiste tool, omdat deze de gegevens versleutelt vóór de upload en restic check aantoont dat de repository nog leesbaar is. Een ERP-backup is een kopie van uw volledige grootboek; deze hoort daarom versleuteld te zijn op hardware die niet deze server is.

Test de restore voordat u deze nodig heeft

Een back-up die niet is getest, is slechts een aanname. Test de back-up op een tweede locatie op dezelfde server, nooit op de live-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 dat van de live-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 hooguit een onverwachte herstart. Bij ERPNext betekent het een schema-migratie. bench migrate herschrijft databasetabellen en kan documentdata wijzigen; dit proces is onomkeerbaar. Een rollback vereist een restore vanuit 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 vastgelegd. Neem dit nummer niet zomaar over zonder het te controleren. Huidige releases staan vermeld op de frappe/erpnext releases page, en de beschikbare image-tags vindt u op Docker Hub. Lees de release notes van de versie waarnaar u wilt overstappen voordat u de migratie uitvoert.

De upgrade zelf begint met een backup en het inschakelen van 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 migratiecommando's 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 essentieel omdat migrate het schema wijzigt terwijl het proces draait. Als een gebruiker een document indient terwijl een tabel slechts gedeeltelijk is gemigreerd, moet u achteraf handmatig records herstellen.

Upgrade één major-versie per keer en maak tussen elke stap een backup. 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 bevat ook overrides/compose.migrator.yaml, wat een container toevoegt die bij elke start bench --site all migrate uitvoert. Dit is handig, maar het betekent ook dat een docker compose up met een gewijzigde tag uw productiedatabase migreert zonder toezicht. Voer bij een bedrijfskritisch systeem een migratie alleen uit als een bewuste beslissing op dat moment.

Een server beveiligen die klantgegevens bevat

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. Die 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 het verbinden met een databaseclient te vergemakkelijken: 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 afzonderlijke netwerkfirewall van de provider.

Schakel tweefactorauthenticatie in bij de Systeeminstellingen voor elk account met de rol Systeembeheerder. Deze rol kan elk document lezen 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 nog een extra wachtwoord per applicatie.

Installeer updates op de host en start de server opnieuw op voor kernel-updates. Voordat u erop vertrouwt dat de stack automatisch weer opstart, controleert u het gegenereerde bestand op een restart-beleid voor elke service, omdat een stack zonder dit beleid na een reboot uitgeschakeld blijft. een Docker Compose-stack automatisch laten opstarten 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 het geval is:

  • Achtergrondtaken hopen zich op, waardoor e-mails en imports minuten of uren te laat aankomen.
  • docker inspect rapporteert containers met "OOMKilled": true of exit code 137.
  • Rapporten die twee seconden duurden, duren nu dertig seconden, 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 deze 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 van ERPNext is achtergrondwerk: het genereren van rapporten en bulk-imports. Meer worker-containers kosten minder dan een grotere server, en zij 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 exacte 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 applicaties 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 het aanmaken onbereikbaar?

De frontend kiest standaard welke site moet worden geserveerd op basis van de HTTP Host-header, dus de sitenaam moet 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, genereer 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 deze bestanden. De configuratiekopie bevat encryption_key; een restore 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, genereer 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.