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

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_dockersetztlinux/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_dockerErstellen 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"
}
]
EOFpython3 -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-hrmsErwartetes 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/.envErwartetes 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:
| Dienst | Image | Beschreibung |
|---|---|---|
| frontend | custom-hrms:1.0 | Nginx, exponiert Port 8080 |
| backend | custom-hrms:1.0 | Gunicorn/WSGI, intern Port 8000 |
| websocket | custom-hrms:1.0 | Node.js/Socket.IO, intern Port 9000 |
| queue-short | custom-hrms:1.0 | Worker für kurze Jobs |
| queue-long | custom-hrms:1.0 | Worker für lange Jobs (Payroll) |
| scheduler | custom-hrms:1.0 | Zeitgesteuerte Frappe-Jobs |
| db | mariadb:11.8 | MariaDB, nur intern |
| redis-cache | redis:8.6-alpine | Session-Cache |
| redis-queue | redis:8.6-alpine | Job-Queue mit Persistenz |
| Einstellung | Wert |
|---|---|
| HTTP-Port (extern) | 8080 (konfigurierbar via HTTP_PUBLISH_PORT) |
| HTTPS-Port (optional) | 443 (mit Traefik-Overlay) |
| Volume: Site-Daten | sites:/home/frappe/frappe-bench/sites |
| Volume: MariaDB | db-data:/var/lib/mysql |
| Volume: Redis-Queue | redis-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 -dPrü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 psErwartetes 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=20Es 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 bashFü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"
exitAuf 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/loginErwartetes 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=10Bei 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:
- HR Settings prüfen
- Abteilungen: HR > Abteilung > Neu
- Mitarbeiter: HR > Mitarbeiter > Neu
- Urlaubstypen definieren: HR > Urlaubstyp (z. B. Jahresurlaub, Krankheit, Überstundenausgleich)
- 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=20Alle 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-cacheneu bauen, Stack neu starten.- Leere Seite / „Site does not exist“
Schritt 6 fehlt noch:
bench new-siteundinstall-appausführen.- „manifest unknown“ beim Start
PULL_POLICY=missingfehlt in der.env. Variable setzen,docker compose up -dwiederholen.- Site-Erstellung schlägt fehl mit „Access denied“
DB_PASSWORDin.envund--db-root-passwordimbench 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_zonewie 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_WORKERSin.envreduzieren.
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
- Docker und Docker Compose auf Linux installieren (Ubuntu/Debian)
- Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
- Docker Compose absichern: Secrets, Healthchecks und Non-Root
- 3-2-1-Backup-Strategie umsetzen
- Mautic mit Docker installieren
Offizielle Quellen: Frappe HRMS auf GitHub · frappe_docker (offizielles Deployment-Repository) · Frappe HRMS Dokumentation


