Frappe inkl. HRMS und vielen weiteren Apps auf der Synology NAS via Portainer – läuft endlich!

Hallo zusammen,

nach wochenlangem Herumprobieren habe ich es endlich geschafft: Frappe mit vielen Zusatzmodulen läuft auf meiner Synology NAS mit Docker und Portainer – und das völlig stabil!

Meine Erfahrung möchte ich gerne mit euch teilen, um anderen viel Zeit und Nerven zu ersparen.

Bei mir laufen inzwischen folgende Apps erfolgreich:

frappe
erpnext
hrms
payments
crm
builder
insights
print_designer
helpdesk
wiki
webshop
lms
drive
website_leads
raven
erpnext_shipping
woocommerce_integration

:wrench: Ausgangspunkt

Gestartet habe ich mit dieser Anleitung von Marius Hosting – sehr empfehlenswert!

Aber ich brauchte zusätzlich das HRMS-Modul, und dessen Integration hat mich beinahe wahnsinnig gemacht. Wochenlang vergeblich versucht, bis ich auf das Docker-Image von @knimer auf Docker Hub** gestoßen bin.

Ab da ging alles sehr schnell – ich war so motiviert, dass ich direkt alle verfügbaren Apps getestet habe. Mit ein paar Anpassungen im Portainer-Stack läuft jetzt alles in einem Rutsch durch.


:file_folder: Schritt 1 – Ordnerstruktur auf der Synology NAS anlegen

Erstellt folgende Verzeichnisse:

/volume1/docker/erpnext/db
/volume1/docker/erpnext/empty-default
/volume1/docker/erpnext/logs
/volume1/docker/erpnext/redis-cache
/volume1/docker/erpnext/redis-queue
/volume1/docker/erpnext/redis-socketio
/volume1/docker/erpnext/sites

:page_facing_up: Schritt 2 – Benötigte Dateien hinzufügen

  1. Leere Datei erstellen:Pfad:
/volume1/docker/erpnext/empty-default/default

Diese Datei überschreibt die nginx-default-Konfiguration (bei mir lief sonst Port 80 in Konflikt).

Sie kann komplett leer sein (0 Bytes) oder einfach nur auskommentierte Zeilen enthalten (# so wie ich es getan habe).

##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
#server {
#        listen 80 default_server;
#        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

#        root /var/www/html;

        # Add index.php to the list if you are using PHP
#        index index.html index.htm index.nginx-debian.html;

#        server_name _;

#        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
#                try_files $uri $uri/ =404;
#        }

        # pass PHP scripts to FastCGI server
        #
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
#}


# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
#       listen 80;
#       listen [::]:80;
#
#       server_name example.com;
#
#       root /var/www/example.com;
#       index index.html;
#
#       location / {
#               try_files $uri $uri/ =404;
#       }
#}

  1. Datei common_site_config.json erstellen:Pfad:
/volume1/docker/erpnext/sites/common_site_config.json

Inhalt:

{
 "db_host": "erpnext-db",
 "db_port": 3306,
 "default_site": "Frappe",
 "developer_mode": 1,
 "redis_cache": "redis://redis-cache",
 "redis_queue": "redis://redis-queue",
 "redis_socketio": "redis://redis-socketio",
 "socketio_port": 9000
}

:shield: Schritt 3 – Rechte setzen

Alle oben genannten Ordner inklusive Unterordner müssen den richtigen Rechten zugewiesen werden.

Siehe dazu die Schritte 13–17 in der Anleitung von MariusHosting.


:brick: Schritt 4 – Stack in Portainer erstellen

In Portainer einen neuen Stack anlegen. Dort meinen YAML-Code (siehe unten) einfügen und wie folgt anpassen:

version: "3.9"

services:
  redis-queue:
    image: redis
    container_name: ERPNext-REDIS-QUEUE
    hostname: redis-queue
    mem_limit: 256m
    mem_reservation: 50m
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    read_only: true
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping || exit 1"]
    volumes:
      - /volume1/docker/erpnext/redis-queue:/data:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5

  redis-cache:
    image: redis
    container_name: ERPNext-REDIS-CACHE
    hostname: redis-cache
    mem_limit: 256m
    mem_reservation: 50m
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    read_only: true
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping || exit 1"]
    volumes:
      - /volume1/docker/erpnext/redis-cache:/data:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5

  redis-socketio:
    image: redis
    container_name: ERPNext-REDIS-SOCKETIO
    hostname: redis-socketio
    mem_limit: 256m
    mem_reservation: 50m
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    read_only: true
     healthcheck:
      test: ["CMD-SHELL", "redis-cli ping || exit 1"]
    volumes:
      - /volume1/docker/erpnext/redis-socketio:/data:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5

  db:
    image: mariadb:10.8-jammy
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake
    container_name: ERPNext-DB
    hostname: erpnext-db
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -u root -pmysqlrootpassword | grep 'mysqld is alive' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 30
      start_period: 30s
    volumes:
      - /volume1/docker/erpnext/db:/var/lib/mysql:rw
      - /etc/localtime:/etc/localtime:ro
    environment:
      MYSQL_ROOT_PASSWORD: mysqlrootpassword
    restart: on-failure:5

  configurator:
    image: knimer/erpnext:v15.20250526
    entrypoint:
      - bash
      - -c
    command:
      - >
        ls -1 apps > sites/apps.txt;
        bench set-config -g db_host erpnext-db;
        bench set-config -gp db_port 3306;
        bench set-config -g redis_cache "redis://redis-cache";
        bench set-config -g redis_queue "redis://redis-queue";
        bench set-config -g redis_socketio "redis://redis-socketio";
        bench set-config -gp socketio_port 9000;
    container_name: ERPNext-CONFIGURATOR
    hostname: configurator
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    environment:
      DB_HOST: erpnext-db
      DB_PORT: 3306
      REDIS_CACHE: redis-cache
      REDIS_QUEUE: redis-queue
      REDIS_SOCKETIO: redis-socketio
      SOCKETIO_PORT: 9000
    restart: "no"
    depends_on:
      redis-queue:
        condition: service_healthy
      redis-cache:
        condition: service_healthy
      redis-socketio:
        condition: service_healthy
      db:
        condition: service_healthy

  backend:
    image: knimer/erpnext:v15.20250526
    container_name: ERPNext-BACKEND
    hostname: backend
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully

  websocket:
    image: knimer/erpnext:v15.20250526
    command:
      - node
      - /home/frappe/frappe-bench/apps/frappe/socketio.js
    container_name: ERPNext-WEBOSCKET
    hostname: websocket
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully

  create-site:
    image: knimer/erpnext:v15.20250526
    entrypoint:
      - bash
      - -c
    command:
      - >
        wait-for-it -t 480 erpnext-db:3306;
        wait-for-it -t 480 redis-cache:6379;
        wait-for-it -t 480 redis-queue:6379;
        wait-for-it -t 480 redis-socketio:6379;
        export start=`date +%s`;
        until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
          [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
          [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
        do
          echo "Waiting for sites/common_site_config.json to be created";
          sleep 5;
          if (( `date +%s`-start > 120 )); then
            echo "could not find sites/common_site_config.json with required keys";
            exit 1
          fi
        done;
        echo "sites/common_site_config.json found";
        bench set-config -g developer_mode 1;
        bench new-site Frappe \
          --no-mariadb-socket \
          --mariadb-root-password=mysqlrootpassword \
          --admin-password=admin \
          --install-app=erpnext \
          --install-app=hrms \
          --install-app=payments \
          --install-app=crm \
          --install-app=builder \
          --install-app=insights \
          --install-app=print_designer \
          --install-app=helpdesk \
          --install-app=wiki \
          --install-app=webshop \
          --install-app=lms \
          --install-app=drive \
          --install-app=website_leads \
          --install-app=raven \
          --install-app=erpnext_shipping \
          --install-app=woocommerce_integration \
          --set-default;
    container_name: ERPNext-CREATE-SITE
    hostname: create-site
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: "no"
    depends_on:
      configurator:
        condition: service_completed_successfully

  queue-default:
    image: knimer/erpnext:v15.20250526
    command: bench worker --queue default
    container_name: ERPNext-QUEUE-DEFAULT
    hostname: queue-default
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully
 
  queue-long:
    image: knimer/erpnext:v15.20250526
    command: bench worker --queue long
    container_name: ERPNext-QUEUE-LONG
    hostname: queue-long
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully
 
  queue-short:
    image: knimer/erpnext:v15.20250526
    command: bench worker --queue short
    container_name: ERPNext-QUEUE-SHORT
    hostname: queue-short
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully
 
  scheduler:
    image: knimer/erpnext:v15.20250526
    command: bench schedule
    container_name: ERPNext-SCHEDULER
    hostname: scheduler
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure:5
    depends_on:
      configurator:
        condition: service_completed_successfully

  frontend:
    image: knimer/erpnext:v15.20250526
    command:
      - nginx-entrypoint.sh
    container_name: ERPNext-FRONTEND
    hostname: frontend
    mem_limit: 1g
    cpu_shares: 768
    security_opt:
      - no-new-privileges:true
    ports:
      - 8344:8080
    volumes:
      - /volume1/docker/erpnext/sites:/home/frappe/frappe-bench/sites:rw
      - /volume1/docker/erpnext/logs:/home/frappe/frappe-bench/logs:rw
      - /volume1/docker/erpnext/empty-default/default:/etc/nginx/sites-enabled/default:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      BACKEND: backend:8000
      FRAPPE_SITE_NAME_HEADER: Frappe
      SOCKETIO: websocket:9000
      UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
      UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
      UPSTREAM_REAL_IP_RECURSIVE: "off"
      PROXY_READ_TIMOUT: 120
      CLIENT_MAX_BODY_SIZE: 50m
    restart: on-failure:5
    depends_on:
      backend:
        condition: service_started
      websocket:
        condition: service_started

a) Nur gewünschte Apps installieren:

In der ERPNext-CREATE-SITE-Sektion alle --install-app=-Zeilen entfernen, die du nicht brauchst.

Achte darauf, keine Leerzeile zu hinterlassen – das führt sonst zum Fehler.

Apps, die voneinander abhängig sind, zeigen dies in den Logs an, falls was fehlt.

b) MySQL Root-Passwort setzen:

  • In ERPNext-DB: Passwort in MYSQL_ROOT_PASSWORD und im Healthcheck (nach -p) anpassen.
  • In ERPNext-CREATE-SITE: Passwort bei mariadb-root-password setzen.

c) Stack starten – los geht’s! :tada:


:clock3: Schritt 5 – Installationszeit

Auf meiner (nicht besonders schnellen) Synology NAS dauert der komplette Vorgang etwa 1,5 Stunden.

Tipp: Im Log des Containers ERPNext-CREATE-SITE kannst du live mitverfolgen, wie die Installation durchläuft.

Installiert wird in folgender Reihenfolge (wichtig wegen App-Abhängigkeiten):

frappe
erpnext
hrms
payments
crm
builder
insights
print_designer
helpdesk
wiki
webshop
lms
drive
website_leads
raven
erpnext_shipping
woocommerce_integration

Jede App sollte ihr Dashboard aktualisieren, keine Fehler, Tracebacks oder Merge-Konflikte auftreten.

Am Ende steht:

*** Scheduler is disabled ***
Current Site set to Frappe

:earth_africa: Schritt 6 – ERPNext starten

Einfach aufrufen unter:

http://deine-nas-ip:8344

Login-Daten:

  • Benutzer: Administrator
  • Passwort: admin

:white_check_mark: Schritt 7 – Aufräumen nach der Installation

Wenn alles funktioniert:

  • Die Container ERPNext-CREATE-SITE und ERPNext-CONFIGURATOR stoppen (sollten auf “exited (0)” stehen).
  • Optional: Diese Container aus dem Stack löschen oder alle Zeilen mit # auskommentieren – dann werden sie nicht mehr mitgestartet, sind aber im Stack weiterhin verfügbar.

:speech_balloon: Fazit

Ich bin mega zufrieden mit der Lösung!

Großer Dank an Marius für die verständliche Anleitung – und an knimer, ohne dessen Docker-Image das bei mir nie geklappt hätte.

Wenn jemand Fragen hat oder seine eigene Lösung teilen will – gerne her damit.

Falls ihr irgendwo hängt – ich war wahrscheinlich schon an genau der Stelle. :upside_down_face:


:package: Developer Mode zurücksetzen

Am schluss könnt ihr noch optional den developer mode wieder deaktivieren.
Einfach dazu die Datei auf /volume1/docker/erpnext/sites/common_site-config.json öffnen und die zeile “developer_mode”: 1, entfernen

1 Like