Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 28.09.2026 · 12 min Lesezeit

linkding mit Docker Compose: Lesezeichen-Manager fürs Team selbst hosten

linkding ist ein schlanker, selbst gehosteter Lesezeichen-Manager mit SQLite, Tags, REST-API und Browser-Erweiterung. Die Anleitung zeigt Installation per Docker Compose, Teamfreigabe, API-Token, Archivierung ohne schweres Chromium-Image, getestetes Backup und Restore mit full_backup sowie ein Update von 1.46.2 auf 1.47.0 und typische Fehlerbilder.

Mit KI erstellt – redaktionelle Prüfung ausstehend

Illustration zur Anleitung mit der Überschrift Lesezeichen im Team, linkding mit Docker, drei Feature-Karten zu REST-API, Archiv und Backup sowie einem abstrakten Dashboard-Mockup mit Lesezeichenliste und Datenbank in Blautönen.

Link-Sammlungen in Browser-Profilen, Wiki-Seiten und Chatverläufen sind in vielen Teams der Normalzustand. Wer Herstellerportale, Doku-Seiten und interne Werkzeuge zentral und durchsuchbar ablegen will, braucht keinen schweren Wissensspeicher. linkding ist ein bewusst schlanker, selbst gehosteter Lesezeichen-Manager: ein Container, eine SQLite-Datei, Tags, Volltextsuche, REST-API und offizielle Browser-Erweiterungen für Firefox und Chrome.

Diese Anleitung führt von der compose.yaml über Erstkonfiguration, API-Token und geteilte Lesezeichen bis zu Backup, Restore, Update und Deinstallation. Grundlage ist ein eigener Testlauf am 28.09.2026 mit linkding 1.47.0. Zwei Praxisfunde vorweg: Der offizielle Backup-Befehl sichert die Datei secretkey.txt nicht mit, nach einem Restore sind deshalb alle Browser-Sitzungen ungültig, API-Token funktionieren aber weiter. Und Adressen im lokalen Netz lädt linkding ab Werk nicht, weil ein SSRF-Schutz greift.

Was linkding ist und wo die Grenzen liegen

linkding ist ein Django-Projekt unter MIT-Lizenz. Laut GitHub-API (abgerufen am 28.09.2026) hat das Repository sissbruecker/linkding 11.239 Sterne, der letzte Push war am 18.09.2026 und das aktuelle Release ist v1.47.0 vom 13.09.2026. Das Projekt wird also aktiv gepflegt.

Der Funktionsumfang ist klar umrissen:

  • Lesezeichen mit Titel, Beschreibung, Notizen und Tags, Suche mit eigener Syntax (zum Beispiel #doku fü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

Die Grenzen sind ebenso klar. linkding kennt keine Gruppen- oder Ordnerrechte: Lesezeichen gehören genau einem Benutzer, und die Freigabe ist ein einfacher Schalter pro Lesezeichen, den die ganze Instanz sieht. Es gibt keine Volltextindizierung der archivierten Seiteninhalte und keine KI-Verschlagwortung. Wer das braucht, ist mit den Alternativen besser bedient: Karakeep bringt KI-Tagging und Volltextsuche mit, Linkwarden setzt auf Collections mit Teamrechten und automatische Archivierung als PDF und Screenshot. Beide benötigen dafür PostgreSQL oder Meilisearch und deutlich mehr Arbeitsspeicher. linkding ist die Wahl, wenn ein Team schnell eine gemeinsame, gut durchsuchbare Linkliste mit wenig Wartung will.

Voraussetzungen und Ressourcen

  • Linux-Host mit Docker Engine und Compose-Plugin (Test: Docker 29 mit Compose v2)
  • Rund 200 MB freier Arbeitsspeicher für das Standard-Image; im Test belegte der Container nach dem Start 160,5 MiB bei einem Limit von 512 MiB
  • Speicherplatz: das Standard-Image ist komprimiert rund 142 MB groß, entpackt meldete Docker 586 MB; die 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

TagInhaltGröße amd64 (komprimiert, Docker Hub)
latest bzw. 1.47.0Grundfunktionen auf Debian-Basisca. 142 MB
latest-alpinewie latest, Alpine-Basis, laut Doku experimentellca. 51 MB
latest-pluszusätzlich Chromium und singlefile-cli für HTML-Snapshotsca. 602 MB
latest-plus-alpinePlus-Variante auf Alpine, experimentellca. 401 MB

Docker Hub listet für alle Varianten Manifeste für amd64, arm64 und arm/v7, ein Raspberry Pi ist also möglich. Die Archivierungs-Doku nennt die Plus-Variante allerdings als nicht verfügbar für ARMv7; dieser Widerspruch ist unbestätigt, wer auf ARMv7 Snapshots braucht, sollte das vorher prüfen.

Für die Plus-Variante nennt die Doku mindestens 1 GB RAM zusätzlich, weil bei jedem neuen Lesezeichen ein Headless-Chromium die Seite rendert. Dazu kommen der Speicherplatz für die Snapshots und die Unzuverlässigkeit bei Seiten mit Bot-Erkennung oder Login. Auf kleinen VMs ist deshalb das Standard-Image plus Browser-Archivierung (siehe unten) die bessere Wahl. Diese Anleitung verwendet das schlanke Image.

Vollständige compose.yaml und .env

Das Projekt liefert im Repository eine docker-compose.yml und eine .env.sample. Die folgende Datei baut darauf auf, pinnt aber die Version, bindet den Port nur an localhost und begrenzt den Speicher:

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-stopped

Einen eigenen healthcheck braucht es nicht: Das Image bringt bereits curl -f http://localhost:9090/health im Abstand von 30 Sekunden mit. Die .env daneben, 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/24

Dateipfade und Parameter

  • ./data wird nach /etc/linkding/data gemountet und enthält db.sqlite3, tasks.sqlite3, secretkey.txt sowie die Ordner assets, favicons und previews.
  • LD_SUPERUSER_PASSWORD leer lassen legt den Benutzer ohne nutzbares Passwort an, sinnvoll nur mit Authentifizierungs-Proxy.
  • LD_CONTEXT_PATH (zum Beispiel linkding/, mit Schrägstrich am Ende) betreibt linkding unter einem Unterpfad.
  • LD_DB_ENGINE=postgres und die LD_DB_*-Variablen schalten auf PostgreSQL um; für Teams bis zu einigen Dutzend Personen reicht SQLite.
  • LD_DISABLE_URL_VALIDATION=True erlaubt URLs, die Djangos Validator ablehnt, etwa Hostnamen mit Unterstrich.

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 -f

Beim ersten Start erzeugt linkding einen geheimen Schlüssel (INFO Generated secret key file), führt die Datenbank-Migrationen aus und legt den Administrator an (INFO Created initial superuser). Bei jedem weiteren Start erscheint stattdessen Skip creating initial superuser, user already exists; ein geändertes Passwort in der .env wird also nicht erneut gesetzt. Der Anwendungsserver uWSGI läuft als Benutzer www-data (UID 33), die Dateien im data-Ordner gehören deshalb 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.com

Funktionsprüfung und Healthcheck

Der Endpunkt /health braucht keine Anmeldung und liefert die laufende Version. Im Test antwortete er etwa 18 Sekunden nach dem Start mit HTTP 200:

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}}'
healthy

Die Anmeldung ist danach unter /login/ möglich. Ein falsches Passwort beantwortet linkding mit HTTP 401, eine erfolgreiche Anmeldung leitet mit 302 auf /bookmarks weiter. Ohne Sitzung führt jeder Aufruf von /bookmarks auf /login?next=/bookmarks.

Erstkonfiguration: Benutzer und Teamfreigabe

Weitere Konten legen Administratoren unter /admin im Bereich Users an. Normale Benutzer ohne Staff-Status kommen nicht in die Administration, im Test landete ein solches Konto beim Aufruf von /admin/ auf der Admin-Anmeldeseite. Für Skripte oder die Einrichtung per Automatisierung funktioniert auch der nicht interaktive Weg:

# 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.

Damit Kolleginnen und Kollegen Lesezeichen sehen, muss jeder Benutzer die Freigabe in den allgemeinen Einstellungen unter Settings, General aktivieren (Profilfeld enable_sharing) und das einzelne Lesezeichen als geteilt markieren. Geteilte Einträge erscheinen für alle angemeldeten Benutzer unter /bookmarks/shared. Im Test war ein als shared angelegtes Lesezeichen dort für ein zweites Konto sichtbar, ein nicht geteiltes nicht. Eine zusätzliche öffentliche Freigabe ohne Anmeldung lässt sich getrennt einschalten; für interne Link-Sammlungen sollte sie aus bleiben.

REST-API und Browser-Erweiterung

API-Token erzeugt jeder Benutzer unter Settings, Integrations, Create API token. Jedes Token bekommt einen Namen, sinnvoll ist eines pro Gerät oder Skript. Das Token wird nur direkt nach dem Anlegen angezeigt; lädt man die Seite neu, ist es nicht mehr sichtbar. Ein Token hat die vollen 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 lieferte im Test HTTP 201 samt ID, Tags und automatisch gesetztem Wayback-Link. Ohne Titel füllt linkding Titel und Beschreibung aus den Metadaten der Seite. Ohne Token antwortet die API mit HTTP 401 und {"detail":"Authentication credentials were not provided."}, mit ungültigem Token mit 401 und {"detail":"Invalid token."}. Weitere Endpunkte gibt es für Tags, Bundles, Assets und das Benutzerprofil.

Die offizielle Erweiterung für Firefox und Chrome speichert die aktuelle Seite mit Tags und sucht in linkding direkt aus der Adressleiste. Einzutragen sind die Basis-URL der Instanz und ein eigenes API-Token. Für Archivierung ohne Plus-Image empfiehlt die Doku die Kombination mit der Erweiterung SingleFile: Diese lädt die Seite so, wie sie im Browser sichtbar ist, an /api/bookmarks/singlefile/ hoch (Datenfeld file, URL-Feld url). In der linkding-Erweiterung schaltet die Option Run Singlefile after adding new bookmark das automatische Archivieren ein. Beide Erweiterungen müssen aus demselben Store stammen, sonst finden sie sich nicht, was vor allem Edge-Nutzer betrifft.

Persistente Daten und Rechte

Alles Wichtige liegt im gemounteten data-Ordner. SQLite läuft im WAL-Modus, neben db.sqlite3 liegen deshalb -wal- und -shm-Dateien. Die Datenbank im laufenden Betrieb einfach zu kopieren ist laut Doku nicht transaktionssicher. Die Rechte richtet der Container beim Start selbst ein: Im Test gehörte eine vom Host mit UID 1000 zurückgespielte Datenbank nach dem Start wieder UID 33. Beim Löschen des Ordners vom Host aus braucht es deshalb sudo.

Netzwerkfreigabe, Reverse Proxy und TLS

linkding spricht selbst nur HTTP auf Port 9090. Für den Teambetrieb gehört ein Reverse Proxy mit TLS davor, der Port bleibt wie in der compose.yaml an 127.0.0.1 gebunden. Wichtig ist der CSRF-Schutz: Passt der Origin-Header nicht zum Host-Header, schlägt die Anmeldung fehl. Im Test mit abweichendem Origin lautete die Antwort HTTP 403 mit CSRF verification failed. Request aborted. Abhilfe schafft entweder LD_CSRF_TRUSTED_ORIGINS=https://links.example.com oder das Durchreichen der Header. Für Nginx nennt die 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 ohne Zusatzkonfiguration durch. Wer einen Authentifizierungs-Proxy wie Authelia vorschaltet, muss die API-Pfade daran vorbeiführen, sonst scheitern Erweiterung und Apps an der Weiterleitung auf die Login-Seite.

Backup und Restore

Der offizielle Weg ist der Befehl full_backup. Er erzeugt ein ZIP mit einer transaktionssicheren Kopie der Datenbank sowie den Ordnern 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).zip

Im Test meldete der Befehl Copied 65 of 65 pages... und Backup created at /etc/linkding/data/backup.zip. Da ohne Plus-Image keine Assets und noch keine Favicons vorhanden waren, enthielt das ZIP nur db.sqlite3. Auffällig: secretkey.txt ist nicht enthalten.

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 -d

Im Test wurde der data-Ordner vollständig gelöscht und aus dem ZIP wiederhergestellt. Danach meldete das Log No migrations to apply., alle drei Benutzerkonten, beide aktiven und das archivierte Lesezeichen waren da, und das vor dem Backup erzeugte API-Token funktionierte weiter, weil Token in der Datenbank liegen. Da linkding eine neue secretkey.txt erzeugte, waren aber alle bestehenden Browser-Sitzungen ungültig; die alte Sitzung landete auf der Login-Seite. Wer das vermeiden will, sichert secretkey.txt zusätzlich per Dateikopie. Für eine reine Lesezeichenliste bietet die Oberfläche zudem einen HTML-Export, der jedoch nur die eigenen Lesezeichen ohne Benutzer, Snapshots und Favicons enthält.

Updates und Rollback-Grenzen

Updates bestehen aus neuem Tag, Pull und Neustart; Migrationen laufen automatisch beim Start. Vorher gehört immer ein full_backup dazu:

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/health

Im Test wurde eine Instanz mit 1.46.2 und einem Lesezeichen auf 1.47.0 gehoben. /health meldete danach "version": "1.47.0", das Log No migrations to apply., und das Lesezeichen war unverändert vorhanden. Zwischen diesen beiden Versionen gab es also keine Schemaänderung. Bei größeren Sprüngen mit Migrationen ist ein Downgrade durch bloßes Zurücksetzen des Tags nicht vorgesehen; die Doku beschreibt keinen Downgrade-Weg. Der verlässliche Rollback ist der alte Tag zusammen mit dem vor dem Update erstellten Backup. latest statt fester Versionen macht Updates bequemer, aber unkontrollierbar.

Typische Fehler mit Diagnose und Lösung

SymptomUrsacheLösung
Login mit HTTP 403 CSRF verification failed. Request aborted.Reverse Proxy schreibt den Host-Header umLD_CSRF_TRUSTED_ORIGINS setzen oder proxy_set_header Host $host
API antwortet 401 Authentication credentials were not provided.Header fehlt oder falsches SchemaHeader exakt als Authorization: Token <token> senden
API antwortet 401 Invalid token.Token gelöscht oder falsch kopiertneues Token unter Integrations erzeugen
HTTP 400 {"url":["Enter a valid URL."]} bei http://intranet_wiki/startDjangos URL-Validator lehnt Unterstriche und Namen ohne TLD abLD_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 optionSSRF-Schutz blockiert private Netzebetroffene Hosts oder Netze in LD_ALLOWED_INTERNAL_HOSTS freigeben
Bind for 127.0.0.1:9090 failed: port is already allocatedPort von anderem Dienst belegtfreien Hostport wählen, Belegung mit ss -ltnp prüfen
Nach Restore sind alle Benutzer abgemeldetsecretkey.txt nicht im Backup, neuer Schlüssel erzeugtDatei separat sichern oder einmal neu anmelden
Snapshots fehlen trotz Plus-Image oder brechen abBot-Erkennung, Login-Seiten, TimeoutLD_SINGLEFILE_TIMEOUT_SEC erhöhen oder SingleFile im Browser nutzen

Saubere Deinstallation

Achtung: Die folgenden Schritte löschen alle Lesezeichen, Benutzer, Token und Snapshots unwiderruflich. Vorher ein full_backup erstellen und außerhalb des Hosts ablegen.

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.0

Anschließend sollte sudo docker ps -a --filter name=linkding nichts mehr ausgeben. Im Browser die Erweiterung entfernen und die API-Token gelten ohnehin nicht mehr, da die Datenbank gelöscht ist.

Testumfang

Getestet mit linkding 1.47.0 (Standard-Image) auf einem Linux-Host mit Docker: Start, /health, Superuser per Umgebungsvariable und per createsuperuser, Login inklusive falschem Passwort, API-Token, Anlegen und Abfragen per REST-API, 401 ohne und mit falschem Token, geteilte Lesezeichen für ein zweites Konto, CSRF-, URL-Validierungs-, SSRF- und Portkonflikt-Fehler, full_backup mit Restore nach Löschen sowie das Update von 1.46.2 auf 1.47.0. Nur aus der Doku übernommen: Plus-Image mit Chromium-Snapshots, Browser-Erweiterungen und SingleFile, Reverse Proxy mit TLS, OIDC, Authentifizierungs-Proxy und PostgreSQL.

Passende Anleitungen auf S-EDV

Quellen

linkdingLesezeichenDocker ComposeSelfhostingREST-APIBackupSQLite