Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung IT-Branche 19.07.2026 · 11 min Lesezeit

ERPNext mit Docker installieren: Vollständiges Open-Source-ERP für KMU

ERPNext ist ein umfassendes Open-Source-ERP-System. Diese Anleitung zeigt, wie Sie Buchhaltung, Lager, CRM und Fertigung per Docker Compose auf einem Linux-Server starten, inklusive Backup und Updates, ohne Lizenzkosten.

Geprüft am 16.10.2026 · für ERPNext 16.51.1

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Lagerregal mit Kartons, kleinem Tower-Server und Ordner in einem hellen Büroraum als Sinnbild für ein ERP-System im Mittelstand

ERPNext deckt Buchführung, Lager, Einkauf und Verkauf, Fertigung, CRM, Projekte und POS ab, als GPL-3.0-Software ohne Lizenzgebühren; Personalwesen liefert die App Frappe HR. Diese Anleitung installiert ERPNext v16 mit dem offiziellen frappe/frappe_docker-Repository per Docker Compose (elf Container) und zeigt die typischen Fallstricke beim ersten Start.

Voraussetzungen

  1. Docker Engine v24.0+ und Docker Compose Plugin v2 (docker compose). Installation: Docker und Docker Compose auf Linux installieren.
  2. Linux-Host (Ubuntu 22.04/24.04 empfohlen), macOS oder Windows mit Docker Desktop; ARM64 offiziell unterstützt.
  3. Mindestens 4 GB RAM zum Testen, 8 GB RAM und 4 CPU-Kerne produktiv. Unter 4 GB stürzen Queue-Worker oder Gunicorn ab (Out-of-Memory, 502/504).
  4. Mindestens 40 GB freier Speicherplatz (Image-Download ca. 2–3 GB, Produktionsempfehlung 160 GB SSD).
  5. Empfohlen: Domain mit DNS für HTTPS über einen Reverse-Proxy (Traefik oder Nginx Proxy Manager).

Schritt 1: Eckdaten und Architektur verstehen

ERPNext v16 besteht aus elf Containern:

EigenschaftWert
Image (ERPNext)frappe/erpnext:v16.51.1 (Docker Hub, Stand 16.10.2026)
Image (Datenbank)mariadb:11.8
Image (Cache/Queue)redis:6.2-alpine (zwei Instanzen)
Öffentlicher Port8080 → Nginx-Frontend
Interne Portsbackend:8000, websocket:9000, db:3306, redis:6379 – alle nur containerseitig
LizenzGPL-3.0, kostenlos, kein Vendor-Lock-in
Architekturx86_64 und ARM64 (Multi-Arch-Images)
VolumeZweckKritisch?
sitesFrappe-Bench-Konfiguration, Site-Daten, UploadsJa – Datenverlust bei Löschung
db-dataMariaDB-DatenbankdateienJa – Datenverlust bei Löschung
redis-queue-dataPersistente Job-Queue für HintergrundaufgabenEmpfohlen
logsAnwendungslogsOptional

configurator und create-site sind Init-Container: Sie enden nach einem Durchlauf korrekt mit Exitcode 0; die übrigen neun laufen dauerhaft.

Verifizieren: Prüfen Sie Docker- und Compose-Version mit docker --version und docker compose version. Sie sollten mindestens Docker version 24.x und Docker Compose version v2.x sehen. Weniger als 4 GB freien RAM? Dann zuerst Ressourcen prüfen: free -h.

Schritt 2: Projektordner anlegen

Legen Sie einen Ordner für den Stack an:

mkdir -p /opt/erpnext
cd /opt/erpnext

Auch ~/erpnext geht; alle Dateien müssen nur im selben Verzeichnis liegen.

Verifizieren: ls -la /opt/erpnext zeigt ein leeres Verzeichnis. Kein Fehler = korrekt.

Schritt 3: .env-Datei mit sicheren Passwörtern anlegen

Legen Sie die .env vor dem ersten Start an; spätere Passwortänderungen bei laufender Datenbank erfordern zusätzliche Schritte.

# /opt/erpnext/.env
# ERPNext Version – exaktes Image-Tag
ERPNEXT_VERSION=v16.51.1

# MariaDB Root-Passwort (NIEMALS den Default "123" oder "changeme" verwenden!)
DB_PASSWORD=sEhrSicheresDbPasswort42

# ERPNext Admin-Passwort (für Login als "Administrator" im Browser)
ADMIN_PASSWORD=nochEinSicheresAdminPw7

# Öffentlicher HTTP-Port (Standard: 8080)
HTTP_PUBLISH_PORT=8080

# Optional: Gunicorn-Worker (Faustregel: 2 × CPU-Kerne + 1)
GUNICORN_WORKERS=2

# Optional: Nginx Upload-Limit und Timeout
CLIENT_MAX_BODY_SIZE=50m
PROXY_READ_TIMEOUT=120

Vergeben Sie unterschiedliche, starke Passwörter für DB_PASSWORD und ADMIN_PASSWORD. Beide landen ungequotet in einem Shell-Befehl des create-site-Containers: nur Buchstaben und Ziffern, kein $, &, ;, Anführungszeichen oder Leerzeichen, z. B. aus openssl rand -hex 24. Dateirechte 600:

chmod 600 /opt/erpnext/.env

Verifizieren: cat /opt/erpnext/.env zeigt die Werte. Beide Passwörter sind unterschiedlich, lang und bestehen nur aus Buchstaben und Ziffern. Dateiberechtigung: ls -la /opt/erpnext/.env → -rw-------.

Schritt 4: compose.yaml anlegen

Die compose.yaml basiert auf der offiziellen pwd.yml aus frappe/frappe_docker für den Betrieb auf einem Host. Die YAML-Anker (&customizable_image, *backend_defaults) sind gültig:

# /opt/erpnext/compose.yaml
# ERPNext v16 – Docker Compose (Standalone, plattformneutral)
# Quelle: github.com/frappe/frappe_docker

x-customizable-image: &customizable_image
  image: frappe/erpnext:${ERPNEXT_VERSION:-v16.51.1}
  pull_policy: always

x-depends-on-configurator: &depends_on_configurator
  depends_on:
    configurator:
      condition: service_completed_successfully

x-backend-defaults: &backend_defaults
  <<: [*depends_on_configurator, *customizable_image]
  volumes:
    - sites:/home/frappe/frappe-bench/sites
    - logs:/home/frappe/frappe-bench/logs
  networks:
    - frappe_network

services:

  # ---------- Datenbank ----------
  db:
    image: mariadb:11.8
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      retries: 10
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake
      - --skip-innodb-read-only-compressed
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeme_now}
      MYSQL_DATABASE: frappe
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - frappe_network

  # ---------- Redis Cache (flüchtig) ----------
  redis-cache:
    image: redis:6.2-alpine
    restart: unless-stopped
    networks:
      - frappe_network

  # ---------- Redis Queue (persistent) ----------
  redis-queue:
    image: redis:6.2-alpine
    restart: unless-stopped
    volumes:
      - redis-queue-data:/data
    networks:
      - frappe_network

  # ---------- Konfiguration (einmaliger Init-Container) ----------
  configurator:
    <<: *customizable_image
    restart: "no"
    command: >
      bash -c "
        bench set-config -g db_host db &&
        bench set-config -gp db_port 3306 &&
        bench set-config -g redis_cache redis://redis-cache:6379 &&
        bench set-config -g redis_queue redis://redis-queue:6379 &&
        bench set-config -gp socketio_port 9000
      "
    environment:
      DB_HOST: db
      DB_PORT: "3306"
      REDIS_CACHE: redis-cache:6379
      REDIS_QUEUE: redis-queue:6379
      SOCKETIO_PORT: "9000"
    volumes:
      - sites:/home/frappe/frappe-bench/sites
      - logs:/home/frappe/frappe-bench/logs
    depends_on:
      db:
        condition: service_healthy
    networks:
      - frappe_network

  # ---------- Site-Erstellung (einmaliger Init-Container) ----------
  create-site:
    <<: *customizable_image
    restart: "no"
    command: >
      bash -c "
        wait-for-it -t 120 db:3306 &&
        wait-for-it -t 120 redis-cache:6379 &&
        wait-for-it -t 120 redis-queue:6379 &&
        export start=$(date +%s) &&
        until [[ -n $(grep -hs ^ sites/common_site_config.json | python3 -c \"import sys, json; config = json.load(sys.stdin); print(config.get('db_host', '').strip())\") ]]; do
          echo 'Warte auf Konfiguration ...' &&
          sleep 5 &&
          if (( $(date +%s) - start > 120 )); then echo 'Timeout!' && exit 1; fi;
        done &&
        echo 'Konfiguration vorhanden, erstelle Site ...' &&
        bench new-site --mariadb-root-password ${DB_PASSWORD:-changeme_now} --admin-password ${ADMIN_PASSWORD:-changeme_admin} --mariadb-user-host-login-scope='%' frontend &&
        bench --site frontend install-app erpnext
      "
    volumes:
      - sites:/home/frappe/frappe-bench/sites
      - logs:/home/frappe/frappe-bench/logs
    depends_on:
      configurator:
        condition: service_completed_successfully
    networks:
      - frappe_network

  # ---------- Backend (Gunicorn) ----------
  backend:
    <<: *backend_defaults
    restart: unless-stopped

  # ---------- WebSocket ----------
  websocket:
    <<: [*depends_on_configurator, *customizable_image]
    restart: unless-stopped
    command: node /home/frappe/frappe-bench/apps/frappe/socketio.js
    volumes:
      - sites:/home/frappe/frappe-bench/sites
      - logs:/home/frappe/frappe-bench/logs
    networks:
      - frappe_network

  # ---------- Queue-Worker (kurze Jobs) ----------
  queue-short:
    <<: *backend_defaults
    restart: unless-stopped
    command: bench worker --queue short,default

  # ---------- Queue-Worker (lange Jobs) ----------
  queue-long:
    <<: *backend_defaults
    restart: unless-stopped
    command: bench worker --queue long,default,short

  # ---------- Scheduler ----------
  scheduler:
    <<: *backend_defaults
    restart: unless-stopped
    command: bench schedule

  # ---------- Frontend (Nginx Reverse Proxy) ----------
  frontend:
    <<: *customizable_image
    restart: unless-stopped
    command: nginx-entrypoint.sh
    environment:
      BACKEND: backend:8000
      FRAPPE_SITE_NAME_HEADER: frontend
      SOCKETIO: websocket:9000
      UPSTREAM_REAL_IP_ADDRESS: ${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1}
      UPSTREAM_REAL_IP_HEADER: ${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For}
      UPSTREAM_REAL_IP_RECURSIVE: ${UPSTREAM_REAL_IP_RECURSIVE:-off}
      PROXY_READ_TIMEOUT: ${PROXY_READ_TIMEOUT:-120}
      CLIENT_MAX_BODY_SIZE: ${CLIENT_MAX_BODY_SIZE:-50m}
    volumes:
      - sites:/home/frappe/frappe-bench/sites
      - logs:/home/frappe/frappe-bench/logs
    ports:
      - "${HTTP_PUBLISH_PORT:-8080}:8080"
    depends_on:
      - websocket
    networks:
      - frappe_network

volumes:
  sites:
  logs:
  db-data:
  redis-queue-data:

networks:
  frappe_network:
    driver: bridge

Wichtig: Der Site-Name frontend im bench new-site-Befehl muss mit FRAPPE_SITE_NAME_HEADER im frontend-Container übereinstimmen, sonst folgt „Site frontend not found“.

Verifizieren: YAML-Syntax prüfen mit docker compose config – die Ausgabe zeigt die aufgelöste Konfiguration ohne Fehlermeldung. docker compose config --services listet alle elf Services.

Schritt 5: Stack starten und Initialisierung abwarten

Starten Sie alle Container im Hintergrund:

cd /opt/erpnext
docker compose up -d

Docker lädt rund 2–3 GB Images (5–15 Minuten), dann legt create-site die Datenbank an und installiert ERPNext. Diesen Prozess nicht abbrechen. Fortschritt verfolgen:

docker compose logs -f create-site

Nach 5–10 Minuten erscheinen Zeilen wie diese (Wortlaut je nach Version), dann endet die Ausgabe:

create-site-1  | Installing frappe...
create-site-1  | Installing erpnext...

Erst wenn create-site mit Exitcode 0 beendet ist, ist die Installation vollständig.

Verifizieren: Alle dauerhaften Dienste müssen laufen. Die Option -a zeigt auch die beendeten Init-Container:

docker compose ps -a

Erwartetes Ergebnis (gekürzt):

NAME                    STATUS
erpnext-db-1            Up (healthy)
erpnext-redis-cache-1   Up
erpnext-redis-queue-1   Up
erpnext-backend-1       Up
erpnext-websocket-1     Up
erpnext-queue-short-1   Up
erpnext-queue-long-1    Up
erpnext-scheduler-1     Up
erpnext-frontend-1      Up
erpnext-configurator-1  Exited (0)
erpnext-create-site-1   Exited (0)

Zwei Init-Container mit Exited (0) sind korrekt, alle anderen sollen Up zeigen. HTTP-Check:

curl -I http://localhost:8080

Erwartete Antwort: HTTP/1.1 200 OK oder 302 Found (Redirect auf Login-Seite).

Schritt 6: Ersteinrichtung im Browser

Öffnen Sie http://localhost:8080 (oder die Server-IP):

  1. Benutzername: Administrator
  2. Passwort: Wert aus ADMIN_PASSWORD in der .env

Danach startet der Setup Wizard (5–10 Minuten): Land, Sprache, Zeitzone, Währung. Mit „Germany“ stehen SKR03 und SKR04 zur Wahl.

Installation prüfen:

docker compose exec backend bench --site frontend list-apps

Erwartete Ausgabe:

frappe
erpnext

Verifizieren: Das ERPNext-Dashboard ist sichtbar, Module wie „Buchhaltung“, „Lager“ und „Einkauf“ erscheinen in der Navigation. Taucht erpnext bei list-apps nicht auf, installieren Sie es nach: docker compose exec backend bench --site frontend install-app erpnext.

Schritt 7: Backups einrichten

Sichern Sie vor dem Produktivstart die Volumes sites (Konfiguration, Uploads) und db-data (MariaDB). Manueller Backup-Befehl:

# Site-Backup inklusive Uploads (landet in sites/frontend/private/backups/)
docker compose exec backend bench --site frontend backup --with-files

# MariaDB-Dump aller Datenbanken direkt aus dem Container
docker compose exec -T db sh -c 'mariadb-dump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' > /opt/erpnext/backup_mariadb_$(date +%Y%m%d).sql

Der Dump nutzt MYSQL_ROOT_PASSWORD im Container, weil DB_PASSWORD in der Host-Shell nicht gesetzt ist. --all-databases ist nötig, weil Frappe die Site-Datenbank unter einem generierten Namen anlegt (db_name in sites/frontend/site_config.json).

Automatisierte Backups mit Offsite-Kopie (S3, Hetzner Storage Box): cron plus Restic; Gesamtstrategie: MySQL & PostgreSQL Backup automatisieren.

Verifizieren: ls -lh sites/frontend/private/backups/ (im Container oder per docker compose exec backend ls /home/frappe/frappe-bench/sites/frontend/private/backups/) zeigt .sql.gz- und .tar-Dateien mit aktuellem Datum.

Schritt 8: Updates durchführen

Neue v16-Minor-Versionen erscheinen mehrmals im Monat. Updates laufen über die .env:

# 1. Backup erstellen (Pflicht vor jedem Update!)
docker compose exec backend bench --site frontend backup --with-files

# 2. Neue Version in .env setzen, z.B.:
#    ERPNEXT_VERSION=v16.52.0 (Beispiel, aktuelle Version siehe Docker Hub)

# 3. Neue Images ziehen und Container neu starten
docker compose pull
docker compose up -d

# 4. Datenbankmigrationen ausführen
docker compose exec backend bench --site frontend migrate

Wichtig: bench migrate (Schritt 4) nie überspringen, sonst drohen Fehler oder inkonsistente Daten.

Lesen Sie vor dem Update die Release Notes. Die Version v16.50.0 enthält Breaking Changes bei den Berechtigungen: ERPNext prüft jetzt Benutzerberechtigungen für Datensätze, die in Buchhaltungs-, Lager-, Anlagen-, Fertigungs- und Projektaktionen verwendet werden, und passt die Rollenrechte unter anderem für Zahlungsbedingungs-Vorlagen, POS-Rechnungen, Seriennummern- und Chargenbündel sowie Adressen an. Prüfen Sie nach dem Update mit einem normalen Benutzerkonto, ob die gewohnten Abläufe noch funktionieren, und ergänzen Sie fehlende Rechte in der Rollenverwaltung.

Im Test lief das Update von v16.37.0 auf v16.50.0 mit genau diesen Befehlen durch: bench version meldete danach ERPNext und Frappe 16.50.0, Anmeldung und ein zuvor angelegter Datensatz blieben erhalten. docker compose ps -a zeigte den Init-Container create-site nach dem Update mit Exited (1), weil Compose ihn beim Update erneut startet. Das ist hier unkritisch, solange die Site erreichbar ist und die Daten vorhanden sind. Am 16.10.2026 lief auch das Update von v16.50.0 auf v16.51.1 mit diesen Befehlen: bench version meldete ERPNext 16.51.1 und Frappe 16.52.0, die Anmeldung gelang, ein zuvor angelegter ToDo-Eintrag war erhalten, ein falsches Passwort wurde mit 401 abgewiesen, und create-site stand wieder mit Exited (1) und der Meldung Site frontend already exists da. v16.51 ergänzt unter anderem die Gruppierung von Gewinn-und-Verlust-Rechnung und Bilanz nach Buchhaltungsdimensionen und korrigiert in 16.51.1 die Bestandsabschlussbuchung (Stock Closing Entry).

Verifizieren: docker compose ps zeigt alle Dienste als Up. docker compose logs backend --tail=20 zeigt keine Fehlermeldungen. Im Browser zeigt der Eintrag Über im Benutzermenü oben rechts die neue Version.

Troubleshooting / Typische Fehler

  1. „create-site nach 10 Minuten nicht beendet“: bei langsamer CPU oder wenig RAM normal; weiter mit docker compose logs -f create-site beobachten, erst bei Exited (1) handeln.
  2. „Site frontend not found“: Site-Name und FRAPPE_SITE_NAME_HEADER weichen ab (Schritt 4). Prüfen: docker compose exec frontend printenv FRAPPE_SITE_NAME_HEADER.
  3. „Access denied for user 'root'“: DB_PASSWORD passt nicht zum Passwort, mit dem das db-data-Volume initialisiert wurde. Nur bei frischer Installation ohne Daten: Volumes löschen und neu starten (löscht alle Daten!): docker compose down -v && docker compose up -d.
  4. 502/504 Bad Gateway: meist Out-of-Memory unter 4 GB RAM; docker compose logs backend --tail=50 zeigt Worker failed to boot. RAM erhöhen oder GUNICORN_WORKERS=1 in .env setzen und neu starten.
  5. MariaDB startet nicht / healthcheck schlägt fehl: --character-set-server=utf8mb4 und --collation-server=utf8mb4_unicode_ci unter command: nicht entfernen.
  6. Nach docker compose down -v sind alle Daten weg: -v löscht alle benannten Volumes inklusive sites und db-data. Normal nur docker compose down.
  7. Keine Echtzeit-Updates / WebSocket-Fehler hinter Reverse-Proxy: Der Proxy muss die Header Upgrade und Connection weiterleiten.
  8. Custom Apps (z. B. Frappe HR) lassen sich nicht nachinstallieren: bench get-app im laufenden Container wirkt nicht dauerhaft. Zusätzliche Apps listen Sie in einer apps.json und bauen mit images/layered/Containerfile aus frappe_docker ein eigenes Image (Datei als BuildKit-Secret).

Häufige Fragen

Kann ich ERPNext auf ARM64 betreiben (Raspberry Pi 5, Apple M-Chip)?

Ja. Die offiziellen Images sind Multi-Arch (linux/amd64, linux/arm64); frappe_docker enthält einen ARM64-Leitfaden.

Wie führe ich ein Backup durch?

docker compose exec backend bench --site frontend backup --with-files (Ablage unter sites/frontend/private/backups/), zusätzlich db-data per mysqldump oder Snapshot (Schritt 7).

Wie aktualisiere ich auf eine neue ERPNext-Version?

Wie in Schritt 8: Backup, ERPNEXT_VERSION anpassen, Images ziehen, neu starten und unbedingt bench --site frontend migrate.

Unterstützt ERPNext deutsche Buchhaltungsanforderungen?

ERPNext ist (teilweise) deutsch übersetzt und bringt SKR03 und SKR04 mit („Germany“ im Wizard). DATEV-Export und weitere deutsche Anforderungen kommen über Community-Apps wie erpnext_germany und erpnext_datev, eingebunden über ein eigenes Image (siehe Troubleshooting). Vollständige GoBD-Konformität mit dem Steuerberater abstimmen.

Kann ich mehrere Sites (Multi-Tenancy) betreiben?

Ja, mit den Multi-Bench-Overrides aus frappe_docker (overrides/compose.multi-bench.yaml) und meist einem Traefik-Proxy. Dieser Standalone-Aufbau unterstützt nur eine Site.

Was ist der Unterschied zwischen Docker Hub und GHCR für ERPNext-Images?

Die offiziellen Beispiele (pwd.yml, compose.yaml) nutzen frappe/erpnext von Docker Hub. Unter ghcr.io/frappe/erpnext war am 29.09.2026 kein öffentliches Image abrufbar.

Fazit

Kritisch sind sichere Passwörter vor dem ersten Start, Geduld bei create-site und Backups vor den ersten echten Daten. Produktiv braucht es HTTPS per Reverse-Proxy und mindestens 8 GB RAM.

Weiterführende Anleitungen und Quellen

  1. Docker und Docker Compose auf Linux installieren
  2. Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
  3. MySQL & PostgreSQL Backup automatisieren
  4. Docker Compose absichern: Secrets, Healthchecks, Non-Root

Offizielle Quellen: frappe/frappe_docker auf GitHub (Referenz-Repository), Frappe Docker Dokumentation und ERPNext Dokumentation.