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
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.
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
Schritt 2 – Benötigte Dateien hinzufügen
- 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;
# }
#}
- 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
}
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.
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!
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
Schritt 6 – ERPNext starten
Einfach aufrufen unter:
Login-Daten:
- Benutzer: Administrator
- Passwort: admin
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.
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.
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