linkding mit Docker Compose: Lesezeichen-Manager fürs Team selbst hosten
linkding ist ein schlanker, selbst gehosteter Lesezeichen-Manager mit SQLite, Tags und REST-API. Die Anleitung zeigt Docker Compose mit 1.47.0, Teamfreigabe, API-Token, Backup und Restore mit full_backup, Update und typische Fehler.
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

Link-Sammlungen in Browser-Profilen und Chatverläufen sind in vielen Teams Normalzustand. linkding ist ein schlanker, selbst gehosteter Lesezeichen-Manager für Herstellerportale, Doku-Seiten und interne Werkzeuge: ein Container, eine SQLite-Datei, Tags, Suche, REST-API und Browser-Erweiterungen für Firefox und Chrome.
Zwei Praxisfunde vorweg: Der offizielle Backup-Befehl sichert secretkey.txt nicht mit, nach einem Restore sind alle Browser-Sitzungen ungültig (API-Token funktionieren weiter). Adressen im lokalen Netz lädt linkding ab Werk nicht, ein SSRF-Schutz greift.
Was linkding ist und wo die Grenzen liegen
linkding ist ein Django-Projekt (MIT). Beschrieben wird v1.47.0.
Der Funktionsumfang ist klar umrissen:
- Lesezeichen mit Titel, Beschreibung, Notizen und Tags, Suche mit eigener Syntax (zum Beispiel
#dokufür einen Tag) - Status für ungelesen, archiviert und geteilt, dazu Bundles als gespeicherte Filter
- Mehrere Benutzer, Verwaltung über die eingebaute Django-Administration unter
/admin - REST-API mit benannten Token für Skripte, Erweiterungen und Apps
- Automatischer Link auf einen Snapshot der Internet Archive Wayback Machine
- Optional HTML-Snapshots der Seiten auf dem eigenen Server (nur im Image
latest-plus) - Anmeldung per Formular, OIDC oder Authentifizierungs-Proxy wie Authelia
Grenzen: keine Gruppen- oder Ordnerrechte. Lesezeichen gehören einem Benutzer, die Freigabe ist ein Schalter pro Lesezeichen für die ganze Instanz. Es gibt keine Volltextindizierung archivierter Seiten und keine KI-Verschlagwortung. Alternativen: Karakeep (KI-Tagging, Volltextsuche) und Linkwarden (Collections mit Teamrechten, Archivierung als PDF und Screenshot), beide mit PostgreSQL oder Meilisearch und mehr RAM. linkding passt für eine gemeinsame, durchsuchbare Linkliste mit wenig Wartung.
Voraussetzungen und Ressourcen
- Linux-Host (x86-64, ARM64 oder ARMv7) mit Docker Engine und Compose-Plugin, 1 CPU-Kern
- Rund 200 MB RAM für das Standard-Image (Test: 160,5 MiB bei Limit 512 MiB)
- Speicher: Standard-Image komprimiert rund 142 MB, entpackt 586 MB, Datenbank startet bei etwa 260 KB
- Für den Betrieb im Team ein DNS-Name und ein Reverse Proxy mit TLS
Image-Varianten und Architekturen
| Tag | Inhalt | Größe amd64 (komprimiert, Docker Hub) |
|---|---|---|
latest bzw. 1.47.0 | Grundfunktionen auf Debian-Basis | ca. 142 MB |
latest-alpine | wie latest: Alpine-Basis, laut Doku experimentell | ca. 51 MB |
latest-plus | zusätzlich Chromium und singlefile-cli für HTML-Snapshots | ca. 602 MB |
latest-plus-alpine | Plus-Variante auf Alpine, experimentell | ca. 401 MB |
Alle Varianten haben Manifeste für amd64, arm64 und arm/v7 (Raspberry Pi möglich). Die Plus-Variante ist laut Archivierungs-Doku für ARMv7 nicht verfügbar, prüfen Sie das für Snapshots auf ARMv7.
Die Plus-Variante braucht laut Doku mindestens 1 GB RAM zusätzlich (Headless-Chromium), Speicher für Snapshots und ist bei Bot-Erkennung oder Login unzuverlässig. Auf kleinen VMs ist das Standard-Image mit Browser-Archivierung (siehe unten) besser, diese Anleitung nutzt es.
Schritt 1: Vollständige compose.yaml und .env
Basis sind docker-compose.yml und .env.sample des Projekts, mit gepinnter Version, Port nur an localhost und Speicherlimit:
services:
linkding:
container_name: linkding
image: sissbruecker/linkding:1.47.0
ports:
# nur lokal, der Reverse Proxy spricht 127.0.0.1:9090 an
- "127.0.0.1:9090:9090"
volumes:
- ./data:/etc/linkding/data
env_file:
- .env
mem_limit: 512m
restart: unless-stoppedDas Image bringt curl -f http://localhost:9090/health alle 30 Sekunden als Healthcheck mit. Die .env ohne echte Geheimnisse:
# Erster Administrator, wird nur angelegt, wenn der Name noch nicht existiert
LD_SUPERUSER_NAME=admin
LD_SUPERUSER_PASSWORD=BitteLangesPasswortSetzen
# Öffentliche Adresse hinter dem Reverse Proxy, mit Protokoll, ohne Pfad
LD_CSRF_TRUSTED_ORIGINS=https://links.example.com
# Hintergrundaufgaben (Favicons, Vorschaubilder, Snapshots) aktiv lassen
LD_DISABLE_BACKGROUND_TASKS=False
# Optional: interne Hosts, deren Metadaten geladen werden dürfen
# LD_ALLOWED_INTERNAL_HOSTS=wiki.firma.local,192.168.10.0/24Dateipfade und Parameter
./datawird nach/etc/linkding/datagemountet und enthältdb.sqlite3,tasks.sqlite3,secretkey.txtsowie die Ordnerassets,faviconsundpreviews.LD_SUPERUSER_PASSWORDleer lassen legt den Benutzer ohne nutzbares Passwort an, sinnvoll nur mit Authentifizierungs-Proxy.LD_CONTEXT_PATH(zum Beispiellinkding/, mit Schrägstrich am Ende) betreibt linkding unter einem Unterpfad.LD_DB_ENGINE=postgresund dieLD_DB_*-Variablen schalten auf PostgreSQL um; für Teams bis zu einigen Dutzend Personen reicht SQLite.LD_DISABLE_URL_VALIDATION=Trueerlaubt URLs, die Djangos Validator ablehnt, etwa Hostnamen mit Unterstrich.
docker compose config läuft ohne Fehler durch, ls -l .env zeigt -rw-------.
Schritt 2: Installation und Start
# Verzeichnis anlegen und Dateien ablegen$ sudo mkdir -p /opt/linkding
$ cd /opt/linkding# compose.yaml und .env wie oben anlegen, dann die .env schützen$ sudo chmod 600 .env# Image laden und starten$ sudo docker compose up -d# Start verfolgen$ sudo docker compose logs -fDer erste Start erzeugt den geheimen Schlüssel (INFO Generated secret key file), führt Migrationen aus und legt den Administrator an (INFO Created initial superuser). Danach erscheint Skip creating initial superuser, user already exists, ein geändertes .env-Passwort wird nicht erneut gesetzt. uWSGI läuft als www-data (UID 33), die Dateien im data-Ordner gehören UID 33.
Wer keine Zugangsdaten in der .env ablegen will, lässt die beiden LD_SUPERUSER_*-Zeilen weg und legt den Administrator interaktiv an:
$ sudo docker compose exec linkding python manage.py createsuperuser --username=admin --email=admin@example.comDas Log enthält Created initial superuser, sudo ls -ln data zeigt db.sqlite3 und secretkey.txt mit Besitzer 33.
Schritt 3: Funktionsprüfung und Healthcheck
/health braucht keine Anmeldung und liefert die Version. Die Antwort HTTP 200 kommt etwa 18 Sekunden nach dem Start:
curl -s http://127.0.0.1:9090/health
{"version": "1.47.0", "status": "healthy"}
# Status aus Docker-Sicht
sudo docker inspect linkding --format '{{.State.Health.Status}}'
healthyAnmeldung unter /login/: falsches Passwort HTTP 401, Erfolg 302 auf /bookmarks.
/health liefert {"version": "1.47.0", "status": "healthy"}, docker inspect meldet healthy.
Schritt 4: Erstkonfiguration: Benutzer und Teamfreigabe
Weitere Konten legen Administratoren unter /admin: Users an (Benutzer ohne Staff-Status kommen nicht hinein). Nicht interaktiv:
# zweiten Administrator ohne Rückfrage anlegen
sudo docker compose exec -e DJANGO_SUPERUSER_PASSWORD='SicheresPasswort' linkding \
python manage.py createsuperuser --noinput --username=admin2 --email=admin2@example.com
Superuser created successfully.Jeder Benutzer muss die Freigabe unter Settings, General aktivieren (Profilfeld enable_sharing) und das Lesezeichen als geteilt markieren. Geteilte Einträge erscheinen für angemeldete Benutzer unter /bookmarks/shared. Die öffentliche Freigabe ohne Anmeldung ist getrennt schaltbar, lassen Sie sie für interne Sammlungen aus.
Ein zweites Konto sieht unter /bookmarks/shared das geteilte Lesezeichen, ein nicht geteiltes nicht.
Schritt 5: REST-API und Browser-Erweiterung
Jeder Benutzer erzeugt API-Token unter Settings, Integrations, Create API token, sinnvoll ist je ein benanntes Token pro Gerät oder Skript. Es wird nur direkt nach dem Anlegen angezeigt und hat alle Rechte seines Benutzers.
export TOKEN=hier-das-Token-einsetzen
# Lesezeichen mit Tags anlegen und für das Team freigeben
curl -s -H "Authorization: Token $TOKEN" -H 'Content-Type: application/json' \
-d '{"url":"https://linkding.link/","title":"linkding Doku","tag_names":["doku","selfhosting"],"shared":true}' \
https://links.example.com/api/bookmarks/
# Liste, archivierte Einträge und Suche nach Tag
curl -s -H "Authorization: Token $TOKEN" https://links.example.com/api/bookmarks/
curl -s -H "Authorization: Token $TOKEN" https://links.example.com/api/bookmarks/archived/
curl -s -H "Authorization: Token $TOKEN" "https://links.example.com/api/bookmarks/?q=%23doku"
# Prüfen, ob eine URL schon gespeichert ist
curl -s -H "Authorization: Token $TOKEN" "https://links.example.com/api/bookmarks/check/?url=https%3A%2F%2Flinkding.link%2F"Der POST liefert HTTP 201 mit ID, Tags und automatischem Wayback-Link. Ohne Titel übernimmt linkding die Seitenmetadaten. Ohne Token kommt HTTP 401 mit {"detail":"Authentication credentials were not provided."}, mit ungültigem Token 401 mit {"detail":"Invalid token."}. Weitere Endpunkte: Tags, Bundles, Assets, Profil.
Die Erweiterung für Firefox und Chrome speichert die Seite mit Tags und sucht aus der Adressleiste. Sie braucht Basis-URL und eigenes API-Token. Für Archivierung ohne Plus-Image empfiehlt die Doku die Erweiterung SingleFile, die die Seite wie im Browser sichtbar an /api/bookmarks/singlefile/ hochlädt (Felder file, url). Die Option Run Singlefile after adding new bookmark in der linkding-Erweiterung automatisiert das. Beide Erweiterungen müssen aus demselben Store stammen (relevant für Edge).
Der POST liefert HTTP 201 mit ID, ein Aufruf ohne Token HTTP 401; die Erweiterung zeigt nach dem Speichern das Lesezeichen in linkding.
Persistente Daten und Rechte
Alles Wichtige liegt im data-Ordner. SQLite läuft im WAL-Modus, neben db.sqlite3 liegen -wal- und -shm-Dateien. Kopieren im Betrieb ist laut Doku nicht transaktionssicher. Der Container setzt die Rechte selbst: Eine mit UID 1000 zurückgespielte Datenbank gehörte nach dem Start wieder UID 33. Löschen vom Host geht nur mit sudo.
sudo ls -ln /opt/linkding/data zeigt db.sqlite3, -wal- und -shm-Datei mit Besitzer 33.
Schritt 6: Netzwerkfreigabe, Reverse Proxy und TLS
linkding spricht nur HTTP auf Port 9090, davor gehört ein Reverse Proxy mit TLS, der Port bleibt an 127.0.0.1. Passen Origin- und Host-Header nicht zusammen, scheitert die Anmeldung am CSRF-Schutz (Test: HTTP 403, CSRF verification failed. Request aborted.). Abhilfe: LD_CSRF_TRUSTED_ORIGINS=https://links.example.com oder die Header durchreichen. Nginx laut Doku:
location / {
proxy_pass http://127.0.0.1:9090;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}Caddy und Apache reichen den Host-Header durch. Bei einem Authentifizierungs-Proxy wie Authelia führen Sie die API-Pfade daran vorbei, sonst scheitern Erweiterung und Apps an der Weiterleitung.
curl -s https://links.example.com/health liefert healthy, und die Anmeldung über die Domain gelingt ohne CSRF verification failed.
Schritt 7: Backup und Restore
Offiziell dient full_backup: ZIP mit transaktionssicherer Datenbankkopie sowie assets, favicons und previews:
# Backup im Container erstellen$ sudo docker compose exec linkding python manage.py full_backup /etc/linkding/data/backup.zip# auf den Host kopieren und datieren$ sudo docker cp linkding:/etc/linkding/data/backup.zip /srv/backup/linkding-$(date +%F).zipAusgabe: Copied 65 of 65 pages... und Backup created at /etc/linkding/data/backup.zip. Das ZIP enthält nur db.sqlite3, secretkey.txt fehlt.
Für den Restore wird das ZIP in einen leeren data-Ordner entpackt:
$ cd /opt/linkding
$ sudo docker compose down
$ sudo mv data data.alt
$ sudo mkdir data
$ sudo unzip -q /srv/backup/linkding-2026-09-28.zip -d data
$ sudo docker compose up -dNach dem Löschen des data-Ordners und der Wiederherstellung aus dem ZIP meldet das Log No migrations to apply., Konten und Lesezeichen (auch das archivierte) sind wieder da, das API-Token funktioniert (Token liegen in der Datenbank). Die neue secretkey.txt macht alle Browser-Sitzungen ungültig. Sichern Sie sie per Dateikopie zusätzlich. Der HTML-Export enthält nur die eigenen Lesezeichen.
unzip -l /srv/backup/linkding-*.zip listet db.sqlite3, nach dem Restore meldet das Log No migrations to apply. und die Lesezeichen sind vorhanden.
Schritt 8: Updates und Rollback-Grenzen
Update: neuer Tag, Pull, Neustart, Migrationen laufen automatisch. Vorher immer full_backup:
$ sudo docker compose exec linkding python manage.py full_backup /etc/linkding/data/vor-update.zip# in compose.yaml den Tag anheben, zum Beispiel 1.46.2 auf 1.47.0$ sudo docker compose pull
$ sudo docker compose up -d
$ curl -s http://127.0.0.1:9090/healthBei einem Update von 1.46.2 auf 1.47.0 gilt: /health meldet "version": "1.47.0", das Log No migrations to apply., die Lesezeichen bleiben unverändert (keine Schemaänderung). Ein Downgrade per Tag ist bei Migrationen nicht vorgesehen. Rollback: alter Tag plus Backup vor dem Update.
/health meldet die neue Version, die Lesezeichen sind unverändert vorhanden.
Typische Fehler mit Diagnose und Lösung
| Symptom | Ursache | Lösung |
|---|---|---|
Login mit HTTP 403 CSRF verification failed. Request aborted. | Reverse Proxy schreibt den Host-Header um | LD_CSRF_TRUSTED_ORIGINS setzen oder proxy_set_header Host $host |
API antwortet 401 Authentication credentials were not provided. | Header fehlt oder falsches Schema | Header exakt als Authorization: Token <token> senden |
API antwortet 401 Invalid token. | Token gelöscht oder falsch kopiert | neues Token unter Integrations erzeugen |
HTTP 400 {"url":["Enter a valid URL."]} bei http://intranet_wiki/start | Djangos URL-Validator lehnt Unterstriche und Namen ohne TLD ab | LD_DISABLE_URL_VALIDATION=True |
Interne Adresse ohne Titel und Vorschaubild, Log: Blocked request to http://192.168.x.x/: Refusing to connect ... Use the LD_ALLOWED_INTERNAL_HOSTS option | SSRF-Schutz blockiert private Netze | betroffene Hosts oder Netze in LD_ALLOWED_INTERNAL_HOSTS freigeben |
Bind for 127.0.0.1:9090 failed: port is already allocated | Port von anderem Dienst belegt | freien Hostport wählen, Belegung mit ss -ltnp prüfen |
| Nach Restore sind alle Benutzer abgemeldet | secretkey.txt nicht im Backup, neuer Schlüssel erzeugt | Datei separat sichern oder einmal neu anmelden |
| Snapshots fehlen trotz Plus-Image oder brechen ab | Bot-Erkennung, Login-Seiten, Timeout | LD_SINGLEFILE_TIMEOUT_SEC erhöhen oder SingleFile im Browser nutzen |
Saubere Deinstallation
Achtung: Die Schritte löschen Lesezeichen, Benutzer, Token und Snapshots unwiderruflich. Erstellen Sie vorher ein full_backup und legen Sie es außerhalb des Hosts ab.
$ cd /opt/linkding
$ sudo docker compose down -v --remove-orphans# Dateien gehören UID 33, deshalb mit sudo löschen$ sudo rm -rf /opt/linkding# nur das eigene Image entfernen, keine globalen Prune-Befehle$ sudo docker rmi sissbruecker/linkding:1.47.0Danach gibt sudo docker ps -a --filter name=linkding nichts mehr aus. Entfernen Sie die Browser-Erweiterung.
Häufige Fragen
Warum sind nach einem Restore alle Browser-Sitzungen ungültig?
Der offizielle Befehl full_backup sichert secretkey.txt nicht mit. Beim Start entsteht ein neuer Schlüssel, der alle Browser-Sitzungen ungültig macht, API-Token funktionieren weiter. Sichern Sie secretkey.txt zusätzlich per Dateikopie.
Warum scheitert die Anmeldung über die Domain mit CSRF verification failed?
Origin- und Host-Header passen nicht zusammen, meist weil der Reverse Proxy den Host-Header umschreibt. Setzen Sie LD_CSRF_TRUSTED_ORIGINS auf die öffentliche Adresse oder reichen Sie den Host-Header durch.
Warum lädt linkding keine Metadaten von Seiten im lokalen Netz?
Ab Werk greift ein SSRF-Schutz, Adressen im lokalen Netz lädt linkding nicht. Interne Hosts, deren Metadaten geladen werden dürfen, tragen Sie in LD_ALLOWED_INTERNAL_HOSTS ein.
Ist das Plus-Image für Snapshots nötig?
Nein. Das Plus-Image braucht laut Doku mindestens 1 GB RAM zusätzlich und ist bei Bot-Erkennung oder Login unzuverlässig. Mit dem Standard-Image archivieren Sie Seiten über die Browser-Erweiterung SingleFile, die die Seite wie im Browser sichtbar hochlädt.
Lassen sich Lesezeichen für einzelne Gruppen freigeben?
Nein. linkding kennt keine Gruppen- oder Ordnerrechte, die Freigabe ist ein Schalter pro Lesezeichen für die ganze Instanz. Collections mit Teamrechten bietet Linkwarden.
Fazit
linkding verwaltet Lesezeichen mit einem Container und einer SQLite-Datei, mit Tags, Suche, REST-API und Browser-Erweiterungen. Binden Sie den Port an 127.0.0.1, setzen Sie LD_CSRF_TRUSTED_ORIGINS für den Reverse Proxy und sichern Sie neben dem full_backup auch secretkey.txt. Wer Teamrechte, Volltextsuche oder KI-Verschlagwortung braucht, ist mit Linkwarden oder Karakeep besser bedient.
Weiterführende Anleitungen und Quellen
- Karakeep mit Docker installieren
- Linkwarden mit Docker installieren
- Nginx als Reverse Proxy mit TLS einrichten
Grundlagen dazu: Docker und Docker Compose auf Linux installieren, Docker Compose absichern.


