Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 29.09.2026 · 10 min Lesezeit Geprüft 05.10.2026

lldap mit Docker Compose: zentrales Benutzerverzeichnis für Selfhosting-Dienste

lldap ist ein schlanker LDAP-Server mit Weboberfläche für zentrale Benutzer und Gruppen. Die Anleitung zeigt Docker Compose mit v0.6.3, Benutzer per API, Leserechte-Konto, Test mit ldapsearch sowie Backup und Restore mit Schlüssel-Seed.

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: lldap mit Docker Compose, Benutzer, Gruppen, LDAP

Laufen im Firmennetz mehr als drei selbst gehostete Dienste, führt jeder eigene Benutzerlisten. Ein zentrales LDAP-Verzeichnis löst das, klassische Server wie OpenLDAP sind dafür aufwendig.

lldap (Light LDAP) ist ein schlanker LDAP-Server mit Weboberfläche für Benutzer, Gruppen und Passwörter. Wichtigster Fund: Mit falschem Schlüssel-Seed startet der Server nach einem Restore nicht mehr.

Was lldap ist und wo die Grenzen liegen

lldap ist ein vereinfachter LDAP-Server in Rust (GPL-3.0). Das neueste Release ist v0.6.3 vom 30.04.2026. Neben LDAP (Port 3890) gibt es eine Weboberfläche und GraphQL-API (Port 17170).

Wofür lldap gut ist:

  • Zentrale Anmeldung für LDAP-fähige Dienste (Gitea, Forgejo, Nextcloud, Jellyfin, Grafana, Vaultwarden, rund 90 Beispiele in example_configs)
  • Benutzerquelle für Authelia, Authentik, Keycloak oder Pocket ID (SSO per OIDC oder Forward-Auth)
  • Zugriffssteuerung per memberOf-Filter, etwa nur git_user bei Gitea

Gewollte Grenzen:

  • Kein vollständiger LDAP-Server: Über LDAP gehen nur Lesezugriffe, Benutzer anlegen und Passwörter ändern.
  • Kein anonymer Bind, jeder Dienst braucht ein Bind-Konto.
  • Kein Active-Directory-Ersatz (keine Gruppenrichtlinien, kein Kerberos, keine Windows-Domäne). Linux-Anmeldung per PAM und nslcd ist dokumentiert.

Voraussetzungen und Ressourcen

  • Linux-Host (x86-64, ARM64 oder ARMv7, Image auch -rootless) mit Docker Engine und Compose-Plugin, 1 CPU-Kern (Compose v2).
  • RAM: etwa 8,1 MiB nach dem Start, Image rund 28 MB.
  • Freier Port für die Weboberfläche (LDAP nur bei externem Zugriff), produktiv Reverse Proxy mit TLS.

Welches Image-Tag: stable, v0.6.3 oder latest?

Die Doku nutzt stable, derzeit identisch mit v0.6.3. latest und datierte Tags sind Nightly-Builds. Die Anleitung pinnt v0.6.3.

TagInhaltEmpfehlung
v0.6.3festes ReleaseProduktivbetrieb, reproduzierbar
stablejeweils neuestes Releasebei automatischen Updates
latest, 2026-09-22Nightly aus dem Hauptzweignur zum Testen
*-rootlessstartet direkt als Benutzernach Rechteeinrichtung empfohlen

Schritt 1: Vollständige compose.yaml und .env

Basis ist docs/install.md mit festem Tag, Bind-Mount und Geheimnissen aus einer .env.

services:
  lldap:
    image: lldap/lldap:v0.6.3
    container_name: lldap
    restart: unless-stopped
    ports:
      # Weboberfläche und GraphQL-API, nur lokal
      - "127.0.0.1:17170:17170"
      # LDAP, nur lokal
      - "127.0.0.1:3890:3890"
    volumes:
      - "./lldap_data:/data"
    environment:
      - UID=1000
      - GID=1000
      - TZ=Europe/Berlin
      - LLDAP_JWT_SECRET=${LLDAP_JWT_SECRET}
      - LLDAP_KEY_SEED=${LLDAP_KEY_SEED}
      - LLDAP_LDAP_BASE_DN=dc=example,dc=com
      - LLDAP_LDAP_USER_PASS=${LLDAP_LDAP_USER_PASS}

Erzeugen Sie die .env mit Zufallswerten:

mkdir -p /opt/lldap/lldap_data
cd /opt/lldap
# Zufällige Geheimnisse erzeugen
printf 'LLDAP_JWT_SECRET=%s\nLLDAP_KEY_SEED=%s\nLLDAP_LDAP_USER_PASS=%s\n' \
  "$(openssl rand -base64 36 | tr -d '/+=')" \
  "$(openssl rand -base64 36 | tr -d '/+=')" \
  "$(openssl rand -base64 24 | tr -d '/+=')" > .env
chmod 600 .env
LLDAP_JWT_SECRET=HIER_ZUFALLSWERT_MIN_32_ZEICHEN
LLDAP_KEY_SEED=HIER_ZUFALLSWERT_MIN_12_ZEICHEN
LLDAP_LDAP_USER_PASS=HIER_START_PASSWORT_ADMIN

ls -l .env zeigt -rw-------, docker compose config zeigt alle drei Variablen mit Zufallswerten.

Dateipfade und Parameter

Parameter oder PfadBedeutung
/data (Host: ./lldap_data)users.db (SQLite mit Benutzern, Gruppen, Passwort-Hashes) und lldap_config.toml (beim ersten Start aus Vorlage angelegt).
LLDAP_KEY_SEEDSeed für den Serverschlüssel der Passwörter, muss gleich bleiben (siehe Backup).
LLDAP_JWT_SECRETsigniert Anmelde-Token. Ein Wechsel meldet alle Sitzungen ab.
LLDAP_LDAP_BASE_DNWurzel des Verzeichnisses.
LLDAP_LDAP_USER_PASSPasswort von admin, gilt nur beim ersten Start.
UID, GIDBenutzer von lldap und Eigentümer in /data (Test: 1000:1000).
LLDAP_JWT_SECRET_FILE, LLDAP_KEY_SEED_FILEDatei mit dem Geheimnis (Docker Secret), hat Vorrang.

Alle Optionen (SMTP, LDAPS, LLDAP_DATABASE_URL) stehen in lldap_config.docker_template.toml. LLDAP_-Variablen überschreiben die Datei, Verschachtelung mit doppeltem Unterstrich (LLDAP_SMTP_OPTIONS__SERVER).

Schritt 2: Installation und Start

$ cd /opt/lldap
$ docker compose up -d
# Startmeldungen prüfen
$ docker compose logs --no-log-prefix | grep -E 'version|Starting|Could not|Successfully'

Beim ersten Start meldete lldap: Konfigurationskopie nach /data/lldap_config.toml, Starting LLDAP version 0.6.3: Migrationen bis Schema 11, die Standardgruppen und Successfully (re)set password for "admin". Die Warnung zum key_seed ist harmlos.

Das Log enthält Starting LLDAP version 0.6.3 und Successfully (re)set password for "admin", ls -ln lldap_data zeigt users.db mit Besitzer 1000.

Schritt 3: Funktionsprüfung und Healthcheck

Das Image bringt einen Healthcheck mit (/app/lldap healthcheck), in der Regel nach 30 Sekunden healthy.

$ docker compose ps
# Weboberfläche: HTTP 200
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:17170/
# Healthcheck manuell (Exit-Code 0)
$ docker exec lldap /app/lldap healthcheck --config-file /data/lldap_config.toml

Die Ausgabe endet mit check_ldap ... Success und check_api ... Success.

docker compose ps zeigt (healthy), curl auf Port 17170 liefert 200, der manuelle Healthcheck endet mit Exit-Code 0.

Schritt 4: Erstkonfiguration: Benutzer, Gruppen und Bind-Konto

Melden Sie sich in der Weboberfläche als admin an und legen Sie Benutzer und Gruppen an. Skripte nutzen die GraphQL-API:

cd /opt/lldap
set -a; . ./.env; set +a
# Anmelde-Token holen
TOKEN=$(curl -s -X POST http://127.0.0.1:17170/auth/simple/login \
  -H 'Content-Type: application/json' \
  -d "{\"username\":\"admin\",\"password\":\"$LLDAP_LDAP_USER_PASS\"}" \
  | python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])')
# Benutzer anlegen
curl -s http://127.0.0.1:17170/api/graphql \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"query":"mutation{createUser(user:{id:\"jdoe\",email:\"jdoe@example.com\",displayName:\"Jana Doe\",firstName:\"Jana\",lastName:\"Doe\"}){id}}"}'
# Gruppe anlegen (Antwort: Gruppen-ID)
curl -s http://127.0.0.1:17170/api/graphql \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"query":"mutation{createGroup(name:\"git_user\"){id}}"}'
# Benutzer der Gruppe mit ID 4 zuordnen
curl -s http://127.0.0.1:17170/api/graphql \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"query":"mutation{addUserToGroup(userId:\"jdoe\",groupId:4){ok}}"}'

Passwörter setzt lldap_set_password im Container:

docker exec lldap /app/lldap_set_password \
  --base-url http://localhost:17170 \
  --admin-username admin --admin-password "$LLDAP_LDAP_USER_PASS" \
  --username jdoe --password 'NeuesPasswort!'

Antwort: Successfully changed jdoe's password, bei falschem Admin-Passwort 401 Unauthorized.

Verbinden Sie Dienste nie mit admin, sondern mit einem Konto wie bind_user in lldap_strict_readonly, bei Passwort-Reset (etwa Authelia) mit lldap_password_manager. Mit diesem Konto lassen sich bind_user alle Benutzer lesen, das Anlegen endet mit Unauthorized user creation.

In der Weboberfläche erscheinen jdoe in der Gruppe git_user und bind_user in lldap_strict_readonly.

Schritt 5: LDAP-Anmeldung mit ldapsearch testen

Prüfen Sie mit ldapsearch (ldap-utils bzw. openldap-clients) oder einem Wegwerf-Container im Compose-Netz (<Verzeichnisname>_default, hier lldap_default):

docker run --rm --network lldap_default alpine:3.20 sh -c \
  "apk add -q openldap-clients && ldapsearch -x -H ldap://lldap:3890 \
  -D 'uid=bind_user,ou=people,dc=example,dc=com' -w 'BindPasswort' \
  -b 'ou=people,dc=example,dc=com' '(uid=jdoe)' uid mail cn memberOf"

Die Suche lieferte uid=jdoe mit memberOf: cn=git_user,…. memberOf-Filter, member an der Gruppe und Bind als Benutzer (ldapwhoami: Success (0)) funktionierten.

Gegenproben:

  • Falsches Bind-Passwort: ldap_bind: Invalid credentials (49)
  • Suche ohne Bind: Inappropriate authentication (48), Anonymous bind not allowed
  • Falsches Web-Passwort und GraphQL ohne Token: HTTP 401
  • Benutzerliste als normaler Benutzer: Unauthorized access to user list

ldapsearch liefert den Eintrag von jdoe mit memberOf: cn=git_user,…, ein falsches Passwort ergibt Invalid credentials (49).

Schritt 6: Praxisbeispiel: Gitea, Authelia und weitere Dienste anbinden

Vorlagen liegen im Repository. Für Gitea und Forgejo nennt example_configs/gitea.md eine Quelle LDAP (via BindDN):

  • Host lldap (Dienstname im Docker-Netz), Port 3890
  • Bind DN: Leserechte-Konto, etwa uid=bind_user,ou=people,dc=example,dc=com
  • User Search Base: ou=people,dc=example,dc=com
  • User Filter nur für Mitglieder von git_user: (&(memberof=cn=git_user,ou=groups,dc=example,dc=com)(|(uid=%[1]s)(mail=%[1]s)))
  • Admin Filter: (memberof=cn=lldap_admin,ou=groups,dc=example,dc=com); Attribute uid, givenName, sn, mail, jpegPhoto

Authelia hat die Implementierung lldap mit passenden Standardwerten, Vorlage:

authentication_backend:
  refresh_interval: '1m'
  ldap:
    implementation: 'lldap'
    address: 'ldap://lldap:3890'
    base_dn: 'DC=example,DC=com'
    user: 'UID=bind_user,OU=people,DC=example,DC=com'
    password: 'REPLACE_ME'

Nextcloud nutzt user_ldap (Repository-Skript mit occ ldap:set-config), Pocket ID synchronisiert Benutzer und Gruppen.

Ein Mitglied von git_user kann sich am angebundenen Dienst anmelden, ein Benutzer ohne diese Gruppe nicht.

Persistente Daten und Rechte

Der Container startet als root, passt /data an und wechselt auf UID/GID. Für -rootless setzen Sie user: "1000:1000", das Verzeichnis muss diesem Benutzer gehören.

Das Admin-Passwort der .env gilt nur beim ersten Start (eine spätere Änderung wirkt nicht). Ändern Sie es in der Weboberfläche, notfalls erzwingt LLDAP_FORCE_LDAP_USER_PASS_RESET=true es beim nächsten Start (danach entfernen).

ls -ln lldap_data zeigt users.db und lldap_config.toml mit Besitzer 1000 1000.

Schritt 7: Netzwerkfreigabe, Reverse Proxy und TLS

  • Port 17170 nur an 127.0.0.1 binden, der Proxy leitet darauf (Traefik: Anleitung zu Forward-Auth mit Authentik und Authelia). Unter Subdomain LLDAP_HTTP_URL setzen.
  • Port 3890 nur bei Zugriff von außerhalb Docker veröffentlichen, auf die interne Schnittstelle begrenzt (zum Beispiel beide Ports nur an 127.0.0.1).
  • LDAP ohne TLS überträgt Bind-Passwörter im Klartext. Über das Netz aktivieren Sie LDAPS mit LLDAP_LDAPS_OPTIONS__ENABLED=true, LLDAP_LDAPS_OPTIONS__CERT_FILE und LLDAP_LDAPS_OPTIONS__KEY_FILE (Port 6360).
  • Geben Sie LDAP nie ins Internet frei, auch nicht mit LDAPS.

ss -ltn | grep -E '17170|3890' zeigt nur 127.0.0.1, die Weboberfläche ist über https://lldap.example.com erreichbar.

Schritt 8: Backup und Restore

Ein Backup braucht /data mit users.db und die .env (LLDAP_KEY_SEED, LLDAP_JWT_SECRET). Kaltes Backup mit kurz gestopptem Container:

$ cd /opt/lldap
$ docker compose stop lldap
$ tar czf /backup/lldap-$(date +%F).tar.gz lldap_data .env compose.yaml
$ docker compose start lldap
# Archiv prüfen
$ tar tzf /backup/lldap-$(date +%F).tar.gz

Prüfung des Restore: jdoe gelöscht (Bind: Invalid credentials (49)), Daten zurückgespielt. Prüfsumme der users.db identisch, Benutzer und Gruppe per ldapsearch wieder da, Bind Success (0).

$ cd /opt/lldap
$ docker compose stop lldap
$ rm -rf lldap_data
$ tar xzf /backup/lldap-2026-09-29.tar.gz lldap_data .env
$ docker compose start lldap

Mit anderem LLDAP_KEY_SEED (verlorene, neu erzeugte .env) startet lldap nicht mehr, der Container hängt in einer Neustart-Schleife:

Error: The private key has changed. ...
The private key encoding the passwords has changed since last successful startup.
Changing the private key will invalidate all existing passwords. If you want to
proceed, restart the server with the CLI arg --force-update-private-key=true or
the env variable LLDAP_FORCE_UPDATE_PRIVATE_KEY=true.

Mit dem ursprünglichen Seed lief der Datenstand sofort wieder. LLDAP_FORCE_UPDATE_PRIVATE_KEY=true macht alle Passwörter ungültig. Sichern Sie die .env getrennt geschützt mit.

Stolperstein: Nach set -a; . ./.env haben exportierte Shell-Variablen Vorrang, eine geänderte .env wirkt nicht. Neue Shell oder unset nutzen.

tar tzf listet lldap_data/users.db und .env, nach dem Restore findet ldapsearch die Benutzer wieder und der Bind liefert Success (0).

Schritt 9: Updates und Rollback-Grenzen

Update: Tag anheben, docker compose pull, docker compose up -d. Schema-Migration beim Start (bis Schema 11).

  • Vor dem Update kaltes Backup ziehen und Release-Notes lesen.
  • Rollback nach Schemamigration ist unzuverlässig: altes Tag und Backup zurückspielen. Kein Watchtower auf latest (Nightly-Builds).

Das Log nennt die neue Version nach Starting LLDAP version, docker compose ps zeigt (healthy), der Bind mit ldapwhoami gelingt.

Typische Fehler mit Diagnose und Lösung

SymptomUrsacheLösung
Container startet ständig neu, Log: The private key has changedanderer LLDAP_KEY_SEED als beim letzten Startursprünglichen Seed aus dem Backup der .env wiederherstellen
Admin-Login schlägt nach Änderung in der .env fehlPasswort aus der Konfiguration gilt nur beim Erststartaltes Passwort nutzen oder einmalig LLDAP_FORCE_LDAP_USER_PASS_RESET=true
Inappropriate authentication (48), Anonymous bind not allowedDienst versucht anonymen BindBind DN und Passwort eines Leserechte-Kontos eintragen
Invalid credentials (49)falsches Passwort, falscher Bind DN, Benutzer fehltDN uid=NAME,ou=people,dc=... und Base DN prüfen
Can't contact LDAP server (-1)lldap läuft nicht oder Dienst ist nicht im selben Docker-Netzdocker compose ps: Netz und Log prüfen
lldap_set_password meldet 401 Unauthorizedfalsches oder leeres Admin-PasswortVariable in der Shell prüfen, Passwort in Anführungszeichen
Dienst zeigt keine Gruppenfalscher Gruppenfilter oder falsches MitgliedsattributGruppen unter ou=groups: Attribut member: Benutzer tragen memberOf
Keine users.db in /dataVerzeichnis für den Container-Benutzer nicht beschreibbarEigentümer auf UID:GID setzen

Mit verbose = true in der lldap_config.toml zeigt das Log jede LDAP-Anfrage samt Attributen.

Saubere Deinstallation

Achtung, Datenverlust: Die Befehle löschen alle Benutzer, Gruppen und Passwort-Hashes unwiderruflich, angebundene Dienste verlieren ihre Anmeldequelle. Ziehen Sie vorher ein Backup.

$ cd /opt/lldap
$ docker compose down -v --remove-orphans
$ docker rmi lldap/lldap:v0.6.3
# Daten und Geheimnisse entfernen
$ rm -rf /opt/lldap

Gehören Dateien in lldap_data einem anderen Benutzer, löschen Sie mit sudo.

Häufige Fragen

Was passiert, wenn der LLDAP_KEY_SEED verloren geht?

Mit einem anderen Seed startet lldap nicht mehr, der Container hängt mit The private key has changed in einer Neustart-Schleife. LLDAP_FORCE_UPDATE_PRIVATE_KEY=true macht alle Passwörter ungültig. Sichern Sie die .env deshalb immer getrennt geschützt mit.

Warum wirkt ein geändertes Admin-Passwort in der .env nicht?

Das Passwort aus der .env gilt nur beim ersten Start. Ändern Sie es in der Weboberfläche, notfalls erzwingt LLDAP_FORCE_LDAP_USER_PASS_RESET=true es beim nächsten Start (danach wieder entfernen).

Mit welchem Konto binden sich Dienste an?

Nie mit admin, sondern mit einem Konto wie bind_user in der Gruppe lldap_strict_readonly. Braucht ein Dienst den Passwort-Reset, etwa Authelia, kommt das Konto in lldap_password_manager.

Ersetzt lldap ein Active Directory?

Nein. lldap bietet keine Gruppenrichtlinien, kein Kerberos und keine Windows-Domäne. Über LDAP gehen nur Lesezugriffe, Benutzer anlegen und Passwörter ändern.

Welches Image-Tag eignet sich für den Betrieb?

Ein festes Release wie v0.6.3. latest und datierte Tags sind Nightly-Builds aus dem Hauptzweig, deshalb kein Watchtower auf latest.

Fazit

lldap bündelt Benutzer und Gruppen für LDAP-fähige Dienste in einem schlanken Container mit Weboberfläche, ohne den Aufwand eines klassischen LDAP-Servers. Binden Sie beide Ports an 127.0.0.1, verbinden Sie Dienste nur über ein Leserechte-Konto und aktivieren Sie LDAPS, sobald Bind-Passwörter über das Netz gehen. Sichern Sie lldap_data immer zusammen mit der .env, denn ohne den ursprünglichen LLDAP_KEY_SEED startet der Server nicht mehr.

Weiterführende Anleitungen und Quellen

Grundlagen dazu: Docker und Docker Compose auf Linux installieren, Docker Compose absichern, Docker-Container automatisch aktualisieren.

Quellen

lldapLDAPDocker ComposeBenutzerverwaltungSelfhostingAutheliaGiteaBackup