dms für br
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗
Dokumentenmanagementsystem speziell für Betriebsräte, aus der Praxis und als Open Source entwickelt. Läuft vollständig im Intranet auf jedem Docker-Host (NAS wie QNAP/Synology, eigener Server, Cloud-VM) – ohne Cloud-Verbindung, ohne externen Zugriff.
🇬🇧 English summary: see below
BR-DMS ist ein Community-Projekt und keine Rechtsberatung.
Soweit gesetzlich zulässig, übernehme ich keine Gewähr für Richtigkeit, Vollständigkeit und Aktualität. Gesetzliche Haftungsregelungen, die sich nicht ausschließen lassen, bleiben unberührt.
@bash backend/scripts/rollentests.sh prüft alle API-Routen je Rolle gegen eine Wegwerf-Datenbank (Docker); läuft vor jedem Deploy und auf GitHublbo://) sowie NAS-Datei-Links: ein eingefügter UNC-Pfad (\\server\freigabe\...) wird automatisch in einen brdmsfile://-Link umgewandelt, der über einen lokal installierten Protokoll-Handler geöffnet wird (umgeht die Browser-Sperre für file://-Netzwerkfreigaben)| Schicht | Technologie |
|---|---|
| Backend | Node.js 24, Fastify 5 + Prisma (TypeScript) |
| Datenbank | PostgreSQL 16 |
| Frontend | React 18 + Vite + Tailwind CSS |
| Webserver | Nginx 1.30 (im Container), HTTPS-Proxy mit Sicherheits-Headern (CSP, HSTS …) |
| Deployment | Docker Compose |
| Verschlüsselung | Dokumente AES-256-GCM (Node.js crypto), Backups AES-256-CBC + PBKDF2 (OpenSSL) |
bash/PowerShell + SSH-ClientInteraktives Skript, das alle benötigten Werte abfragt (Datenpfad, Datenbankpasswort,
JWT-/Verschlüsselungsschlüssel, Admin-Account, Ports, SMTP, WatchFolder) und daraus eine
fertige .env erzeugt:
bash installation.sh
# oder direkt: python3 setup_wizard.py
Werte in [eckigen Klammern] sind Vorschläge – Enter übernimmt sie. Felder ohne Vorschlag
(Admin-Passwort, SMTP-Zugangsdaten) sind Pflicht und werden erneut abgefragt; bei Ja/Nein-Fragen gilt
mit Enter der Großbuchstabe ([J/n] = Ja). Schlüssel, Datenbankpasswort und APP_URL erzeugt der
Assistent selbst. Eine vorhandene .env lädt er als Vorschlag – bei der Frage nach den Schlüsseln
dann immer „behalten“ wählen. Beim NAS ist der SSH-Benutzer das Verwaltungskonto (nicht admin);
ihm gehören die Dateien (PUID), als Gruppe schlägt der Assistent administrators statt der Gruppe
aller Konten vor. Die .env schreibt er mit chmod 600. Jede Frage mit Beispiel und Standardwert:
Handbuch, Kapitel 12.3.
Im ersten Schritt wählst du das Zielsystem:
.env ins Projektverzeichnis – danach direkt weiter mit Schritt 2 unten..env wird eine nas.env erzeugt, die zum NAS
kopiert wird, passend zu den SSH-Deploy-Skripten (deploy_komplett.py / deploy_update.sh).Danach (auf dem Zielhost):
mkdir -p <DATA_PATH>/{storage,postgres,logs,watch_inbox,backups,certs}
bash proxy/generate-selfsigned-cert.sh <DATA_PATH> <Hostname> <Host-IP> # Zertifikat MUSS vor dem ersten Start existieren
docker compose up -d --build # legt beim ersten Start automatisch den Admin aus ADMIN_EMAIL/ADMIN_PASSWORD an
docker compose logs -f backend # Start beobachten (Strg+C beendet nur die Anzeige)
Der Assistent zeigt diese Befehle am Ende fertig ausgefüllt an. Danach sofort den ENCRYPTION_KEY
aus der .env in einen Passwort-Manager kopieren.
Browser öffnen: https://<Host-IP>:8443 (Zertifikatswarnung beim ersten Aufruf ist normal, siehe HTTPS aktivieren für Details/Alternativen). Frontend/Backend haben keinen eigenen Host-Port – der Proxy ist der einzige Zugriffsweg.
1. .env anlegen (im Projektverzeichnis, nicht committen):
cp .env.example .env
nano .env # jedes BITTE_AENDERN ersetzen, siehe Tabelle unten
2. Bei NAS-Betrieb zusätzlich .env.deploy anlegen (steuert nur die Deploy-Skripte, keine Secrets):
cp .env.deploy.example .env.deploy # NAS_USER, NAS_HOST, DATA_PATH eintragen
Die .env einmalig per SCP auf die NAS kopieren, den Quellstand danach mit den Deploy-Skripten
übertragen (siehe Deployment-Skripte).
3. Zertifikat erzeugen (muss vor dem ersten Start existieren, sonst startet der Proxy-Container nicht):
cd <DATA_PATH> # bei NAS: auf der NAS per SSH
bash proxy/generate-selfsigned-cert.sh <DATA_PATH> <Hostname> <Host-IP>
4. Container starten:
docker compose up -d --build
Beim ersten Start legt das Backend alle Tabellen und den ersten Admin-Account aus
ADMIN_EMAIL/ADMIN_PASSWORD an – Passwort nach dem ersten Login ändern.
5. Browser öffnen:
https://<Host-IP>:8443
(Zertifikatswarnung beim ersten Aufruf ist normal bei selbstsigniertem Zertifikat, siehe HTTPS aktivieren.)
Vollständige Vorlage: .env.example
Pflichtfelder:
| Variable | Beschreibung | Generieren mit |
|---|---|---|
POSTGRES_PASSWORD | Datenbankpasswort – nur Buchstaben, Ziffern, - und _ ($ % @ / + stören in .env bzw. Datenbank-URL) | openssl rand -hex 24 |
JWT_SECRET | Token-Signaturschlüssel | openssl rand -hex 32 |
ENCRYPTION_KEY | AES-256 Dokumentschlüssel | openssl rand -hex 32 |
NAS_IP | IP-Adresse bzw. Hostname des Docker-Hosts | hostname -I |
APP_URL | URL für Passwort-Reset-Mails (muss die Proxy-HTTPS-Adresse sein, siehe unten) | https://<Host-IP>:8443 |
ADMIN_EMAIL / ADMIN_PASSWORD | Login-Daten des ersten Admin-Accounts – wird beim ersten Start automatisch angelegt | frei wählbar |
Fehlt einer dieser Werte oder steht noch ein Platzhalter darin, startet das Backend bewusst nicht und
nennt den Grund im Log (docker compose logs backend, Zeilen mit [Konfiguration]). Ausnahmen mit
Standardwert: ADMIN_EMAIL ([email protected]), PUID/PGID (1000/100), Proxy-Ports (8443/8080).
Ohne SMTP_* gibt es keine E-Mails, alles andere läuft normal. ADMIN_* wird nur beim allerersten
Start gelesen. Nach Änderungen an der .env: docker compose up -d.
Kritisch: Der
ENCRYPTION_KEYmuss separat gesichert werden. Bei Verlust sind alle Dokumente dauerhaft unlesbar.ADMIN_PASSWORDnach dem ersten Login sofort in den Einstellungen ändern – wird nur beim Seed gelesen, danach nicht mehr automatisch synchronisiert.
| Variable | Beschreibung | Standard |
|---|---|---|
WATCH_FOLDER_ENABLED | Watch-Folder aktivieren | false |
UPDATE_CHECK_ENABLED | Täglicher Hinweis auf neue Major-/Minor-Releases (GitHub) für Vorsitz, Stellvertretung, Admin | true |
SYSTEM_USER_ID | Konto, dem Importe zugeordnet werden (ID per Klick in der Benutzerverwaltung kopieren) | erstes Admin-Konto |
WATCH_INBOX_PATH | Pfad des Eingangsordners auf dem Host (unabhängig vom Docker-Verzeichnis) | DATA_PATH/watch_inbox |
Eingangsordner auf einen separaten, eigenständig freigegebenen Pfad legen (empfohlen):
WATCH_INBOX_PATH=/share/kp # QNAP (SMB-Freigabe)
# WATCH_INBOX_PATH=/volume1/br-dms-eingang # Synology (SMB-Freigabe)
# WATCH_INBOX_PATH=/srv/br-dms-eingang # generischer Docker-Host
Auf NAS-Systemen erhalten einliefernde User dann per SMB nur Zugriff auf diesen Ordner – nie auf das restliche Docker-Verzeichnis. Auf einem generischen Host entsprechend über die dort übliche Freigabe-/Berechtigungslösung einschränken.
Erwartete Unterordner im Watch-Folder:
watch_inbox/
├── anhoerung_99/
├── anhoerung_102/
├── anhoerung_102_ausserordentlich/
├── abmahnung/
├── bewerbung/
├── bewerbung_alternativ/
├── zeitmodell_87/
├── betriebsvereinbarung/
├── arbeitgeber_info/
├── arbeitsschutz/
├── schriftverkehr/
├── protokoll/
└── sonstiges/
| Kategorie | Frist |
|---|---|
| §99 BetrVG – Einstellung/Versetzung | 5 Jahre |
| §102 BetrVG – Kündigung | 5 Jahre |
| §87 BetrVG – Zeitmodelländerung | 5 Jahre |
| Bewerbung | 3 Monate |
| Alternative Bewerbung | 1 Monat |
| Abmahnung | 3 Jahre |
| Information des Arbeitgebers | 5 Jahre |
| Arbeits- und Gesundheitsschutz | 5 Jahre |
| Schriftverkehr | 5 Jahre |
| Sitzungsprotokoll | 4 Jahre |
| Betriebsvereinbarung | 10 Jahre |
| Sonstiges | 5 Jahre |
Alle Fristen sind in den Einstellungen (VORSITZ/ADMIN) individuell anpassbar. Das Löschdatum eines einzelnen Dokuments lässt sich im Bearbeiten-Dialog ändern (z. B. nach falscher Kategorie).
Der Reverse-Proxy-Container (proxy/) ist keine optionale Zusatzkomponente, sondern der einzige Zugriffsweg: Frontend und Backend haben keinen eigenen Host-Port (siehe Ports), und der Proxy terminiert TLS und führt beide unter einer gemeinsamen HTTPS-Adresse zusammen. Ohne ein gültiges Zertifikat unter <DATA_PATH>/certs/ startet der Proxy-Container gar nicht – das Zertifikat muss also schon vor dem allerersten docker compose up existieren (siehe Schritt 1 unten).
Interimslösung (sofort einsatzbereit, selbstsigniert):
bash proxy/generate-selfsigned-cert.sh <DATA_PATH> br-nas 192.168.1.100
Alle Namen und IP-Adressen angeben, unter denen BR-DMS aufgerufen wird – das Zertifikat gilt nur für diese. Gültig 825 Tage; zum Erneuern erneut ausführen und docker compose restart proxy.
Browser zeigen eine Warnung ("Verbindung nicht privat" o.ä.), bis das Zertifikat einmalig pro PC als vertrauenswürdig importiert ist. Dazu <DATA_PATH>/certs/fullchain.pem auf den PC kopieren und in br-dms.crt umbenennen:
Zertifikat von der internen Firmen-CA (schnellste Lösung, falls vorhanden): Falls eure IT bereits eine eigene interne Zertifizierungsstelle betreibt (z.B. via Active Directory Certificate Services – erkennbar daran, dass interne Seiten wie ein ERP-System HTTPS mit einem "Ausgestellt von"-Feld zeigen, das nicht auf eine bekannte öffentliche CA wie Let's Encrypt/DigiCert verweist, sondern auf einen firmeneigenen Namen), reicht eine Bitte an die IT: ein Zertifikat für br-nas ausstellen lassen, mit br-nas als Subject Alternative Name (SAN), nicht nur als CN. Wird meist als .pfx-Datei (Zertifikat + privater Schlüssel gebündelt, passwortgeschützt) ausgeliefert – vor der Verwendung nach PEM konvertieren:
openssl pkcs12 -in zertifikat.pfx -clcerts -nokeys -out <DATA_PATH>/certs/fullchain.pem
openssl pkcs12 -in zertifikat.pfx -nocerts -nodes -out <DATA_PATH>/certs/privkey.pem
Wird nur von firmeneigenen, domain-verwalteten Geräten automatisch als vertrauenswürdig erkannt (private Geräte würden weiterhin warnen) – für den normalen Login/BR-DMS-Betrieb an Büro-PCs aber völlig ausreichend, und sofort verfügbar ohne auf die öffentliche DNS-Zone angewiesen zu sein.
Echtes, überall vertrauenswürdiges Zertifikat (zusätzlich empfohlen, v.a. für öffentlich zugängliche Bereiche wie den Kummerkasten): Per Let's-Encrypt-DNS-Challenge für eine echte Subdomain (z.B. br-dms.example.de) – dafür wird kein offener Port 80/443 ins Internet benötigt, nur die Möglichkeit, einmalig (und bei jeder Erneuerung, alle ~90 Tage) einen TXT-Eintrag in der öffentlichen DNS-Zone der Domain zu setzen (nicht zu verwechseln mit dem internen Windows-/Active-Directory-DNS – meist ein getrenntes System beim Domain-Registrar/Hoster). Wird von jedem Gerät weltweit automatisch vertraut, auch privaten Handys ohne Firmenverwaltung. Ergebnis (fullchain.pem + privkey.pem) einfach anstelle der bisherigen Dateien nach <DATA_PATH>/certs/ legen und docker compose restart proxy – keine weiteren Änderungen nötig.
cd <DATA_PATH>
docker compose up -d --build
(auf NAS-Systemen ggf. mit sudo bzw. dem plattformeigenen Docker-Pfad, siehe Ausgabe des Installationsassistenten)
Danach erreichbar unter https://<NAS-IP-oder-Hostname>:${PROXY_HTTPS_PORT:-8443} (Standard-Ports 443/80 sind bewusst nicht Default, da viele NAS – v.a. QNAP – diese schon für die eigene Verwaltungsoberfläche belegen; in .env auf 443/80 änderbar, falls frei).
Frontend und Backend haben keine eigenen Host-Ports mehr (ports:-Blöcke wurden entfernt, sobald HTTPS über den Proxy bestätigt lief) – aller Zugriff läuft ausschließlich über den HTTPS-Proxy.
Nicht vergessen: APP_URL in .env auf die neue HTTPS-Adresse umstellen (z.B. https://192.168.1.100:8443, später https://br-dms.example.de). Der Wert steuert sowohl den Link in Passwort-Reset-E-Mails als auch die erlaubte CORS-Origin im Backend (backend/src/app.ts) – bei einer veralteten APP_URL blockiert das Backend sonst Anfragen von der neuen Adresse.
| Skript | Plattform | Übertragung |
|---|---|---|
deploy_update.sh | Linux / macOS | tar über SSH |
deploy_update.ps1 | Windows (OpenSSH + tar sind ab Windows 10 eingebaut) | tar über SSH |
deploy_komplett.py | überall mit Python 3 (Host braucht python3 ≥ 3.8) | Python-Programm über SSH, ohne tar |
Alle drei übertragen dieselben Dateien aus deploy_dateien.txt und verhalten sich gleich:
backend/src, backend/prisma und frontend/src werden auf dem Host ersetzt – gelöschte
oder umbenannte Dateien bleiben dort nicht liegen. Übertragen wird zuerst in einen
Zwischenordner; bricht die Verbindung ab, bleibt der alte Stand vollständig erhalten..env, Datenbank, Dokumente, Zertifikate und Backups auf dem Host werden nie angefasst./share/…, Synology /volume…) oder über
DOCKER_BIN in .env.deploy festgelegt; die angezeigten Rebuild-Befehle passen dazu.cp .env.deploy.example .env.deploy # einmalig: NAS_USER, NAS_HOST, DATA_PATH
./deploy_update.sh --trocken # Probelauf: zeigt nur, was übertragen würde
./deploy_update.sh # übertragen, danach Rebuild-Befehle anzeigen
./deploy_update.sh --bauen # übertragen und direkt neu bauen/starten
Unter Windows entsprechend .\deploy_update.ps1 -Trocken bzw. -Bauen, mit Python
python3 deploy_komplett.py --trocken bzw. --bauen.
Vor dem Übertragen prüft npm audit Backend und Frontend auf bekannte Lücken (hoch/kritisch;
überspringen mit OHNE_AUDIT=1); deploy_update.sh lässt außerdem die Rollentests laufen und bricht
ab, wenn eine Rolle mehr oder weniger darf als vorgesehen (braucht Docker; OHNE_TESTS=1 überspringt). Gebaut wird mit frischen Basis-Images
(compose pull postgres proxy && compose build --pull && compose up -d), damit Sicherheitsupdates
von node, nginx und postgres auch auf dem Host ankommen. Neue Lücken meldet außerdem Dependabot
(.github/dependabot.yml), den eigenen Code prüft CodeQL (.github/workflows/codeql.yml) –
beides unter GitHub → Security.
Für Vorführungen lässt sich eine komplett getrennte Instanz mit erfundenen Demodaten auf dem lokalen PC starten (fiktive Firma mit ~250 Beschäftigten, 9er-Gremium, Sitzungen, Dokumente, Gehaltshistorie usw.):
./demo/demo.sh start # bauen, starten, Demodaten einspielen → https://localhost:8444
./demo/demo.sh reset # alles löschen und frisch einspielen
./demo/demo.sh reset tarif # … tarifgebunden: Flächentarif EG 1–17, 3 Stufen
./demo/demo.sh reset angelehnt # … tarifangelehnt: eigene Gehaltstabelle Gruppe 1–6 (Standard)
./demo/demo.sh stop # anhalten, Daten bleiben
./demo/demo.sh logs # Backend-Log verfolgen
./demo/demo.sh entfernen # Demo komplett löschen: Container, Volumes, Images, Schlüssel (fragt nach)
Anmeldung z.B. mit s.kroeger / Demo2026! (Vorsitz), f.albers (JAV) oder u.tiedemann (SBV). Die Demo hat eigene Container, Volumes,
Schlüssel und Port und läuft neben einer echten Installation. Demodaten lassen sich nicht über echte
Daten spielen: Das Skript schreibt nur in eine leere Datenbank und bricht sonst ab.
Soll das Gremium die Demo auf dem NAS ausprobieren, schaltet demo_modus.sh (im Datenverzeichnis, läuft
auf QNAP, Synology und Linux) die Installation vorübergehend um – mit Sicherung der echten Daten außerhalb
der 30-Tage-Rotation, ausgeschaltetem Mailversand und Watch-Folder und eingetippter Bestätigung vor jedem
Löschen:
sudo bash demo_modus.sh status # was läuft gerade?
sudo bash demo_modus.sh demo # echte Daten sichern, Demodaten einspielen
sudo bash demo_modus.sh neu # Demo von vorn beginnen
sudo bash demo_modus.sh echt # echte Daten samt ursprünglicher .env zurückholen
Dasselbe Befehl für Befehl für QNAP und Synology: Handbuch, Kapitel 13.4.
backup.sh/restore.sh lesen DATA_PATH aus der .env im selben Verzeichnis – beide Skripte
müssen also im DATA_PATH-Verzeichnis liegen (dort, wo auch docker-compose.yml und .env
liegen; wird beim Deploy automatisch mitkopiert):
# Backup erstellen (DB + Storage):
cd <DATA_PATH>
sudo bash backup.sh
# Backup wiederherstellen (interaktiv):
cd <DATA_PATH>
sudo bash restore.sh
Backups landen in <DATA_PATH>/backups/ und werden nach 30 Tagen automatisch gelöscht.
Versionsprüfung: Neben jedes Backup legt backup.sh eine info_<Zeit>.txt mit Version, Commit und
Prüfsumme des Datenbankschemas. restore.sh vergleicht das mit dem laufenden System: Ein Backup einer
neueren Version wird nicht eingespielt (erst das System aktualisieren, erzwingen mit --trotzdem).
Ein älteres Backup wird eingespielt – das Schema wird dafür vorher komplett geleert, das Backend
ergänzt beim Start die seither neuen Tabellen und Felder, und restore.sh wartet, bis das geklappt hat.
Verschlüsselte Backups: Steht ein BACKUP_KEY in der .env (openssl rand -hex 32, der
Einrichtungsassistent erzeugt ihn), verschlüsselt backup.sh beide Dateien komplett
(db_<Zeit>.sql.gz.enc, storage_<Zeit>.tar.gz.enc, ohne Klartext-Zwischendatei). Verfahren:
AES-256-CBC mit 256-Bit-Schlüssel (OpenSSL), Schlüsselableitung per PBKDF2 (HMAC-SHA256,
600.000 Iterationen, eigener Zufalls-Salt je Datei) aus dem BACKUP_KEY, der selbst ein 256-Bit-Zufallswert ist.
Ohne Schlüssel schreibt es wie bisher unverschlüsselt und warnt – der Datenbank-Dump enthält dann
Protokolle, Personal- und Gehaltsdaten im Klartext. Nur so verschlüsselt sollte eine Kopie das NAS
verlassen (z. B. zur Sicherung bei der IT). restore.sh erkennt verschlüsselte Backups und prüft den
Schlüssel, bevor etwas überschrieben wird. Notfall ohne Skript:
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -pass pass:<BACKUP_KEY> -in db_<Zeit>.sql.gz.enc | gunzip > db.sql
Konzept, Einrichtung (QNAP/Synology, Snapshots, Kopie außer Haus), Wiederherstellung und Vordrucke stehen
ausführlich in BR-DMS_Backup-Anleitung.pdf
(neu erzeugen: python3 generate_backup_anleitung.py).
ENCRYPTION_KEY und BACKUP_KEY gehören ausgedruckt an einen sicheren Ort außerhalb des NAS –
ohne sie sind Dokumente bzw. Backups nicht wiederherstellbar.
Empfehlung: Tägliche Ausführung via Aufgabenplaner (QNAP/Synology) bzw. cron auf einem
generischen Docker-Host.
Wichtig beim Restore auf einem anderen/neuen System: Der
ENCRYPTION_KEYin der.envmuss schon vor dem Restore exakt dem Schlüssel des Quellsystems entsprechen – sonst lassen sich die wiederhergestellten Dokumente nicht mehr entschlüsseln (siehe Umgebungsvariablen).
Was als Nächstes kommt (Betriebsversammlung, BR-/JAV-Wahl) und was bewusst nicht geplant ist,
steht in der ROADMAP.md.
Das vollständige Handbuch (96 Seiten, mit Screenshots aus der Demo) liegt als PDF im Repository:
BR-DMS_Handbuch.pdf
Aufbau: Teil I beschreibt alle Funktionen, Teil II die Administration, Teil III die Technik (Installation, Betrieb, Backup, Datenbank). Neu erzeugen: Demo starten, node tools/handbuch-screenshots/screenshots.mjs, dann python3 generate_manual.py.
| Dienst | Port | Erreichbar |
|---|---|---|
| Proxy (HTTPS) | 8443 | Intranet – einziger Zugriffsweg, siehe HTTPS aktivieren |
| Proxy (HTTP → Redirect) | 8080 | Intranet |
| Frontend | – | Nur Docker-intern (kein eigener Host-Port) |
| Backend | – | Nur Docker-intern (kein eigener Host-Port) |
| PostgreSQL | – | Nur Docker-intern |
BR-DMS – entwickelt von Tim van der Hoven · Programmierung mit Unterstützung von Claude (Anthropic)
Kontakt: [email protected]
Copyright © 2026 Tim van der Hoven. Veröffentlicht unter der GNU Affero General Public License v3.0 (AGPL-3.0): Nutzung, Änderung und Weitergabe sind erlaubt, solange der Urheberhinweis erhalten bleibt und Änderungen – auch bei Bereitstellung als gehosteter Dienst – unter derselben Lizenz offengelegt werden.
Dieses System ist für den internen Betrieb im Intranet konzipiert und nicht für den Internetzugang ausgelegt.
BR-DMS is a self-hosted document and meeting management system for German works councils (Betriebsräte). It runs entirely inside the company intranet on any Docker host – a NAS (QNAP, Synology), an own server or a VM – with no cloud connection, no external access and no telemetry.
Please note: BR-DMS is built around German labour law (Betriebsverfassungsgesetz, BetrVG): statutory deadlines under §§ 87, 99 and 102, works council and youth representation elections, works meetings, the roles of JAV (youth and trainee representatives) and SBV (representatives of severely disabled employees). The user interface and the manual are German only.
Website: br-dms.de · Online demo: demo.br-dms.de (fictitious sample data) · Contact: [email protected]
.eml/.msg) as documents,
an inbox fed by upload or a watched network folderNode.js 24 · Fastify 5 · Prisma · PostgreSQL 16 · React 18 · Vite · Tailwind CSS · Nginx · Docker Compose
git clone https://github.com/tvanderhoven/br-dms.git
cd br-dms
bash installation.sh # interactive setup wizard (German), writes the .env
Then follow the commands the wizard prints (create the data folders, generate the certificate,
docker compose up -d --build) and open https://<host-ip>:8443. Details: sections
Schnellstart and HTTPS aktivieren above, or chapter 12 of the
manual.
To try it locally with sample data: ./demo/demo.sh start → https://localhost:8444
(login e.g. s.kroeger / Demo2026!).
Copyright © 2026 Tim van der Hoven. Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). Developed with the help of Claude (Anthropic). Bug reports and questions are welcome as GitHub issues or by e-mail – in English or German.