Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Cloud / Hosting 26.06.2026 · 10 min Lesezeit Geprüft 01.11.2026

Frappe HRMS mit Docker installieren: Vollständiges Open-Source-HR-System mit Gehaltsabrechnung

Frappe HRMS mit Docker betreiben: eigenes Image mit ERPNext und HRMS bauen, Stack mit compose.yaml starten, Site anlegen und HRMS einrichten. Mit Backup, Updates und typischen Fehlern, Kern getestet mit ERPNext 16.52 und HRMS 16.52.

Werbung Mit * 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

Titelbild: Frappe HRMS mit Docker installieren, Personal, Urlaub, Abrechnung

Frappe HRMS ist ein quelloffenes HR-System auf Basis des Frappe-Frameworks und ERPNext mit über 13 Modulen, darunter Mitarbeiterverwaltung, Urlaub, Gehaltsabrechnung, Spesen, Onboarding, Schichtplanung und Recruiting. Die Anleitung richtet sich an IT-Verantwortliche in Unternehmen mit 10 bis 500 Beschäftigten, die HRMS selbst gehostet mit Docker Compose betreiben wollen.

Voraussetzungen

  • Docker Engine v23.0+ (BuildKit als Standard) und Docker Compose v2, siehe Docker und Docker Compose auf Linux installieren
  • Linux-Host oder VM mit x86-64-CPU (Ubuntu 22.04/24.04 LTS empfohlen). Die Compose-Datei von frappe_docker setzt linux/amd64.
  • Mindestens 4 GB RAM (8 GB empfohlen), 4 CPU-Kerne, 30 GB freier SSD-Speicher.
  • Internetzugang beim Build (GitHub, PyPI, npm)
  • Git
  • Optional: Reverse Proxy für HTTPS, etwa Traefik als Docker-Reverse-Proxy einrichten

Schritt 1: frappe_docker klonen und apps.json erstellen

Die offiziellen Images frappe/erpnext enthalten nur Frappe und ERPNext. Für HRMS bauen Sie mit frappe_docker ein eigenes Image:

# mkdir -p /opt/frappe-hrms
# cd /opt/frappe-hrms
# git clone https://github.com/frappe/frappe_docker.git
# cd frappe_docker

Erstellen Sie die apps.json im frappe_docker-Verzeichnis. HRMS setzt ERPNext voraus (required_apps in hrms/hooks.py):

cat > apps.json <<'EOF'
[
  {
    "url": "https://github.com/frappe/erpnext",
    "branch": "version-16"
  },
  {
    "url": "https://github.com/frappe/hrms",
    "branch": "version-16"
  }
]
EOF

python3 -m json.tool apps.json gibt das Array mit den Einträgen erpnext und hrms ohne Fehlermeldung aus.

Schritt 2: Custom-Image mit BuildKit-Secret bauen

Der Build übergibt die apps.json als BuildKit-Secret, damit Zugangstokens nicht in der Image-History landen, lädt die Apps und kompiliert die Assets, in 15 bis 30 Minuten:

docker build \
  --no-cache \
  --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
  --build-arg=FRAPPE_BRANCH=version-16 \
  --secret=id=apps_json,src=apps.json \
  --tag=custom-hrms:1.0 \
  --file=images/layered/Containerfile .

Hinweis für ARM64-Hosts (Apple M-Chips, Raspberry Pi 5): Die offizielle compose.yaml setzt platform: linux/amd64; bauen Sie mit --platform=linux/arm64 und entfernen Sie die platform-Zeilen. ARMv7 wird nicht unterstützt.

Hinweis zu RAM beim Build: Unter 4 GB RAM bricht der Asset-Build häufig ab (Segmentation Fault oder OOMKilled). Ein älteres Node.js hilft nicht, Frappe 16 verlangt Node.js 24.

# docker images custom-hrms

Erwartetes Ergebnis: eine Zeile custom-hrms mit Tag 1.0. Fehlt es, prüfen Sie die Build-Ausgabe auf Netzwerkfehler oder OOMKilled.

Schritt 3: Projektordner und .env-Datei anlegen

Legen Sie im Projektordner die .env an. Setzen Sie vor dem ersten Start ein eigenes Datenbank-Passwort, der Beispielwert ist unsicher:

# cd /opt/frappe-hrms
# /opt/frappe-hrms/.env
# ── Custom-Image (nach eigenem Build) ────────────────────────
CUSTOM_IMAGE=custom-hrms
CUSTOM_TAG=1.0
PULL_POLICY=missing

# ── Datenbank ────────────────────────────────────────────────
# PFLICHT: sicheres Passwort setzen (mind. 16 Zeichen, ohne $ und Anführungszeichen)
DB_PASSWORD=Ihr_Sicheres_DB_Passwort_2026

# ── Gunicorn (Formel 2 x CPU-Kerne + 1, bei 4 GB RAM besser 2 bis 4) ─
GUNICORN_WORKERS=4
GUNICORN_THREADS=4
GUNICORN_TIMEOUT=120

# ── Web-Port ─────────────────────────────────────────────────
HTTP_PUBLISH_PORT=8080

# ── Optionale Einstellungen ───────────────────────────────────
# CLIENT_MAX_BODY_SIZE=50m
# PROXY_READ_TIMEOUT=120s
# FRAPPE_SITE_NAME_HEADER=meinefirma.localhost
# LETSENCRYPT_EMAIL=admin@meinefirma.de
# grep -E "CUSTOM_IMAGE|DB_PASSWORD|PULL_POLICY" /opt/frappe-hrms/.env

Erwartetes Ergebnis: Alle drei Variablen erscheinen mit Ihren Werten. Vermeiden Sie $ im Passwort, Compose wertet es als Variable aus. PULL_POLICY=missing ist zwingend, sonst scheitert Docker mit „manifest unknown“ an der Registry.

Schritt 4: compose.yaml anlegen

Legen Sie die compose.yaml in /opt/frappe-hrms/ an. Die Datei definiert die neun Dienste des Stacks und den einmaligen Configurator:

DienstImageBeschreibung
frontendcustom-hrms:1.0Nginx, exponiert Port 8080
backendcustom-hrms:1.0Gunicorn/WSGI, intern Port 8000
websocketcustom-hrms:1.0Node.js/Socket.IO, intern Port 9000
queue-shortcustom-hrms:1.0Worker für kurze Jobs
queue-longcustom-hrms:1.0Worker für lange Jobs (Payroll)
schedulercustom-hrms:1.0Zeitgesteuerte Frappe-Jobs
dbmariadb:11.8MariaDB, nur intern
redis-cacheredis:8.6-alpineSession-Cache
redis-queueredis:8.6-alpineJob-Queue mit Persistenz
EinstellungWert
HTTP-Port (extern)8080 (konfigurierbar via HTTP_PUBLISH_PORT)
HTTPS-Port (optional)443 (mit Traefik-Overlay)
Volume: Site-Datensites:/home/frappe/frappe-bench/sites
Volume: MariaDBdb-data:/var/lib/mysql
Volume: Redis-Queueredis-queue-data:/data
# /opt/frappe-hrms/compose.yaml
# Frappe HRMS: Docker Compose (plattformneutral)
# Voraussetzung: Custom-Image custom-hrms:1.0 muss gebaut sein (Schritt 2)

x-customizable-image: &customizable_image
  image: ${CUSTOM_IMAGE:-custom-hrms}:${CUSTOM_TAG:-1.0}
  pull_policy: ${PULL_POLICY:-missing}

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

x-backend-defaults: &backend_defaults
  <<: [*customizable_image, *depends_on_configurator]
  restart: unless-stopped
  volumes:
    - sites:/home/frappe/frappe-bench/sites

services:
  configurator:
    <<: *customizable_image
    restart: on-failure
    entrypoint:
      - bash
      - -c
    command:
      - >
        ls -1 apps > sites/apps.txt &&
        bench set-config -g db_host $$DB_HOST &&
        bench set-config -gp db_port $$DB_PORT &&
        bench set-config -g redis_cache "redis://$$REDIS_CACHE" &&
        bench set-config -g redis_queue "redis://$$REDIS_QUEUE" &&
        bench set-config -gp socketio_port $$SOCKETIO_PORT;
    environment:
      DB_HOST: ${DB_HOST:-db}
      DB_PORT: ${DB_PORT:-3306}
      REDIS_CACHE: ${REDIS_CACHE:-redis-cache:6379}
      REDIS_QUEUE: ${REDIS_QUEUE:-redis-queue:6379}
      SOCKETIO_PORT: 9000
    volumes:
      - sites:/home/frappe/frappe-bench/sites
    depends_on:
      db:
        condition: service_healthy
      redis-cache:
        condition: service_started
      redis-queue:
        condition: service_started

  backend:
    <<: *backend_defaults
    environment:
      GUNICORN_WORKERS: ${GUNICORN_WORKERS:-2}
      GUNICORN_THREADS: ${GUNICORN_THREADS:-4}
      GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120}

  frontend:
    <<: *customizable_image
    restart: unless-stopped
    command: nginx-entrypoint.sh
    environment:
      BACKEND: backend:8000
      SOCKETIO: websocket:9000
      FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
      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
    ports:
      - "${HTTP_PUBLISH_PORT:-8080}:8080"
    depends_on:
      - backend
      - websocket

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

  queue-short:
    <<: *backend_defaults
    command: bench worker --queue short,default

  queue-long:
    <<: *backend_defaults
    command: bench worker --queue long,default,short

  scheduler:
    <<: *backend_defaults
    command: bench schedule

  db:
    image: mariadb:11.8
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-ChangeMeNow123!}
      MARIADB_AUTO_UPGRADE: 1
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 60s
      interval: 5s
      timeout: 5s
      retries: 5
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake

  redis-cache:
    image: redis:8.6-alpine
    restart: unless-stopped

  redis-queue:
    image: redis:8.6-alpine
    restart: unless-stopped
    volumes:
      - redis-queue-data:/data

volumes:
  sites:
  db-data:
  redis-queue-data:
# docker compose -f /opt/frappe-hrms/compose.yaml config --quiet && echo "YAML valide"

Erwartetes Ergebnis: YAML valide ohne Fehlermeldung. Bei Syntaxfehlern prüfen Sie Einrückungen und YAML-Anchors (& / <<:).

Schritt 5: Stack starten

Starten Sie alle Dienste. Der Configurator schreibt dabei einmalig die Bench-Konfiguration:

# cd /opt/frappe-hrms
# docker compose up -d

Prüfen Sie nach etwa 60 Sekunden den Status. Bricht up beim ersten Start mit container ...-db-1 is unhealthy ab, hat die MariaDB-Initialisierung länger gedauert als der Healthcheck erlaubt; führen Sie docker compose up -d erneut aus:

# docker compose ps

Erwartetes Ergebnis: Alle dauerhaften Dienste zeigen „running“, configurator zeigt „Exited (0)“, er läuft nur einmal:

NAME                        STATUS
frappe-hrms-backend-1       running
frappe-hrms-db-1            running (healthy)
frappe-hrms-frontend-1      running
frappe-hrms-queue-long-1    running
frappe-hrms-queue-short-1   running
frappe-hrms-redis-cache-1   running
frappe-hrms-redis-queue-1   running
frappe-hrms-scheduler-1     running
frappe-hrms-websocket-1     running
frappe-hrms-configurator-1  Exited (0)

Prüfen Sie außerdem die Backend-Logs:

# docker compose logs backend --tail=20

Es erscheinen keine Fehler zu Port, Volume oder Image.

Schritt 6: Frappe-Site erstellen und HRMS installieren

Der Stack läuft jetzt, enthält aber noch keine Site.

Öffnen Sie eine Shell im Backend-Container:

# docker compose exec backend bash

Führen Sie im Container die folgenden Befehle aus und ersetzen Sie meinefirma.localhost durch Ihren Site-Namen. Das Root-Passwort ist DB_PASSWORD aus der .env. --mariadb-user-host-login-scope=% ist nötig, weil sich Backend und Worker aus anderen Containern mit der Datenbank verbinden:

# Site anlegen (Datenbank + Admin-Account erstellen)
bench new-site meinefirma.localhost \
  --mariadb-user-host-login-scope=% \
  --db-root-password Ihr_Sicheres_DB_Passwort_2026 \
  --admin-password IHR_ADMIN_PASSWORT

# ERPNext installieren (Pflicht als HRMS-Abhängigkeit)
bench --site meinefirma.localhost install-app erpnext

# HRMS installieren
bench --site meinefirma.localhost install-app hrms

# Host-Namen für korrekte URL-Auflösung setzen
bench --site meinefirma.localhost set-config host_name "http://localhost:8080"

# Zeitzone für korrekte Lohnperioden setzen (DACH-empfohlen)
bench --site meinefirma.localhost set-config time_zone "Europe/Berlin"

exit

Auf einem CPU-Kern dauert new-site rund 4 Minuten, die ERPNext-Installation rund 10 Minuten, HRMS zusätzlich einige Minuten. Der Build des Custom-Images mit version-16 dauert rund 5 Minuten, Site-Anlage sowie Installation von ERPNext und HRMS auf 4 CPU-Kernen zusammen rund 3 Minuten. Danach antworten Anmeldung und HRMS-Oberfläche mit HTTP 200, ein falsches Passwort mit 401, die Urlaubsarten wie Casual Leave und Sick Leave sind angelegt, und bench backup läuft fehlerfrei. HRMS 16.51 ergänzt unter anderem einen Filter nach Beschäftigungsart in den Anwesenheitsberichten. HRMS 16.52 erlaubt laut Release Notes regionalen Lohnregeln, Arbeitgeberbeiträge nach der Gehaltsstruktur in die Gehaltsabrechnung aufzunehmen, setzt Gehaltsabrechnungen bei einer gescheiterten Buchung aus der Lohnabrechnung auf Entwurf zurück und verhindert Vorschüsse mit Betrag null oder darunter. HRMS 16.53 ergänzt laut Release Notes Anträge auf Ausgleichsurlaub in der mobilen Web-App (PWA), eine Rechts-nach-links-Darstellung und eine Rückfrage vor dem Stornieren von Anträgen; außerdem prüfen Interview-Feedback und Gehaltsstruktur-Zuweisungen die Zugriffsrechte strenger. Der Branch version-16 liefert immer den neuesten Stand der Reihe 16; welche Versionen in Ihrem Image stecken, zeigt bench version.

# curl -I -H 'Host: meinefirma.localhost' http://localhost:8080/login

Erwartetes Ergebnis: HTTP/1.1 200 OK. Ohne passenden Host-Header antwortet Frappe mit 404, weil die Site über den Hostnamen gewählt wird. Im Browser erscheint unter http://localhost:8080 die Anmeldemaske. Login: Administrator / das bei --admin-password gesetzte Passwort (nicht das MariaDB-Root-Passwort!).

# docker compose logs frontend --tail=10

Bei upstream connect error ist das Backend noch nicht bereit; wiederholen Sie den Aufruf nach 30 Sekunden. Für den Browserzugriff über localhost oder die Server-IP setzen Sie FRAPPE_SITE_NAME_HEADER=meinefirma.localhost in der .env und führen docker compose up -d aus.

Schritt 7: HRMS einrichten und erste Mitarbeiterdaten anlegen

Der Setup-Assistent fragt Firmenname, Währung (EUR), Land und Zeitzone ab. Legen Sie danach unter Apps > HRMS die Grunddaten an:

  1. HR Settings prüfen
  2. Abteilungen: HR > Abteilung > Neu
  3. Mitarbeiter: HR > Mitarbeiter > Neu
  4. Urlaubstypen definieren: HR > Urlaubstyp (z. B. Jahresurlaub, Krankheit, Überstundenausgleich)
  5. Gehaltskomponenten einrichten: Payroll > Gehaltskomponente (Grundgehalt, Sozialabgaben, Boni)

Die REST-API steht für alle DocTypes bereit. Zur Härtung des Stacks siehe Docker Compose absichern: Secrets, Healthchecks und Non-Root.

Öffnen Sie in der Frappe-Oberfläche HR > Mitarbeiter. Die Liste lädt ohne Fehler. Unter Installierte Apps (Einstellungen > Über) müssen beide Apps erscheinen: erpnext und hrms.

Schritt 8: Backup und Updates

Sichern Sie die Site regelmäßig und kopieren Sie das Backup auf externen Speicher:

# Manuelles Backup
docker compose exec backend bench --site meinefirma.localhost backup

# Backup-Dateien liegen in:
# sites/meinefirma.localhost/private/backups/

Die Grundlagen einer externen Sicherung beschreibt 3-2-1-Backup-Strategie umsetzen.

Für Updates auf eine neue HRMS-Version:

# 1. apps.json bei Bedarf aktualisieren
# 2. Neues Custom-Image bauen (CUSTOM_TAG in .env auf 1.1 erhöhen)
docker build \
  --no-cache \
  --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
  --build-arg=FRAPPE_BRANCH=version-16 \
  --secret=id=apps_json,src=/opt/frappe-hrms/frappe_docker/apps.json \
  --tag=custom-hrms:1.1 \
  --file=/opt/frappe-hrms/frappe_docker/images/layered/Containerfile \
  /opt/frappe-hrms/frappe_docker/

# 3. Stack neu starten
docker compose up -d

# 4. Datenbank-Migrationen ausführen
docker compose exec backend bench --site meinefirma.localhost migrate
# docker compose ps
# docker compose logs backend --tail=20

Alle Dienste zeigen wieder „running“, migrate endet ohne Fehler.

Troubleshooting / Typische Fehler

„No module named hrms“ im Backend-Log

HRMS fehlt in apps.json, oder das Image wurde danach nicht neu gebaut. Lösung: Image mit --no-cache neu bauen, Stack neu starten.

Leere Seite / „Site does not exist“

Schritt 6 fehlt noch: bench new-site und install-app ausführen.

„manifest unknown“ beim Start

PULL_POLICY=missing fehlt in der .env. Variable setzen, docker compose up -d wiederholen.

Site-Erstellung schlägt fehl mit „Access denied“

DB_PASSWORD in .env und --db-root-password im bench new-site-Befehl stimmen nicht überein.

Segmentation Fault beim Image-Build

meist unter 4 GB RAM; mehr RAM oder Swap zuweisen.

PermissionError nach Update

Rechte auf dem sites-Volume passen nicht. Lösung: docker compose exec backend chown -R frappe:frappe /home/frappe/frappe-bench/sites

Falsche Lohnperioden / Zeitzone

time_zone wie in Schritt 6 setzen und Worker neu starten: docker compose restart queue-long queue-short scheduler

OOMKilled Worker unter Last

Zu wenig RAM. Mehr RAM zuweisen oder GUNICORN_WORKERS in .env reduzieren.

Häufige Fragen

Kann ich Frappe HRMS ohne ERPNext betreiben?

Nein. HRMS setzt ERPNext als Pflicht-App voraus, vor allem für die Buchhaltungsintegration (Lohnbuchungen, Kostenstellen, Kontenplan); die apps.json enthält deshalb zuerst ERPNext, dann HRMS.

Wie richte ich HTTPS mit meinem Domainnamen ein?

Das frappe_docker-Repository enthält ein Traefik-Overlay unter overrides/compose.traefik.yaml. Setzen Sie LETSENCRYPT_EMAIL und SITES_RULE in der .env und starten Sie mit docker compose -f compose.yaml -f overrides/compose.traefik.yaml up -d. Zum Traefik-Setup: Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten.

Wie betreibe ich mehrere Unternehmen (Mandantenfähigkeit)?

Frappe betreibt mehrere Sites mit getrennten Datenbanken in einer Instanz. Je Firma führen Sie bench new-site firma2.localhost --mariadb-user-host-login-scope=% --db-root-password ... --admin-password ... und die App-Installationen aus; für die Auflösung über Domainnamen setzen Sie FRAPPE_SITE_NAME_HEADER in der .env.

Funktioniert der Stack auf einem ARM64-Server (Raspberry Pi 5, Apple M)?

Ja, mit eigenem ARM64-Build (Hinweis in Schritt 2) und 8 GB RAM. ARMv7 (32-Bit) ist ungeeignet.

Wie exportiere ich Gehaltsabrechnungen als PDF?

Über das Frappe-PDF-Rendering mit Chromium aus dem Standard-Image: Im „Gehaltszettel“ erzeugt „Drucken“ ein PDF. Auf ARM64 müssen Sie ggf. CHROMIUM_BIN manuell setzen.

Wie führe ich ein vollständiges Backup durch?

docker compose exec backend bench --site meinefirma.localhost backup erzeugt einen Datenbank-Dump und ein Datei-Backup unter sites/meinefirma.localhost/private/backups/ im sites-Volume. Kopieren Sie es regelmäßig auf externen Speicher.

Fazit

Frappe HRMS deckt Gehaltsabrechnung, Urlaubsverwaltung und Onboarding als aktiv gepflegtes Open-Source-Projekt ab. Nach eigenem Image-Build und bench new-site sind Updates planbar. Planen Sie mindestens 4 GB RAM und SSD-Speicher ein und prüfen Sie Lohnläufe vor dem Produktivbetrieb mit eigenen Gehaltsstrukturen.

Weiterführende Anleitungen und Quellen

  1. Docker und Docker Compose auf Linux installieren (Ubuntu/Debian)
  2. Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
  3. Docker Compose absichern: Secrets, Healthchecks und Non-Root
  4. 3-2-1-Backup-Strategie umsetzen
  5. Mautic mit Docker installieren

Offizielle Quellen: Frappe HRMS auf GitHub · frappe_docker (offizielles Deployment-Repository) · Frappe HRMS Dokumentation