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

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

Titelbild: linkding mit Docker Compose, Lesezeichen, Archiv, Team

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:

  1. Lesezeichen mit Titel, Beschreibung, Notizen und Tags, Suche mit eigener Syntax (zum Beispiel #doku für einen Tag)
  2. Status für ungelesen, archiviert und geteilt, dazu Bundles als gespeicherte Filter
  3. Mehrere Benutzer, Verwaltung über die eingebaute Django-Administration unter /admin
  4. REST-API mit benannten Token für Skripte, Erweiterungen und Apps
  5. Automatischer Link auf einen Snapshot der Internet Archive Wayback Machine
  6. Optional HTML-Snapshots der Seiten auf dem eigenen Server (nur im Image latest-plus)
  7. 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

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

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

Das 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/24

Dateipfade und Parameter

  1. ./data wird nach /etc/linkding/data gemountet und enthält db.sqlite3, tasks.sqlite3, secretkey.txt sowie die Ordner assets, favicons und previews.
  2. LD_SUPERUSER_PASSWORD leer lassen legt den Benutzer ohne nutzbares Passwort an, sinnvoll nur mit Authentifizierungs-Proxy.
  3. LD_CONTEXT_PATH (zum Beispiel linkding/, mit Schrägstrich am Ende) betreibt linkding unter einem Unterpfad.
  4. LD_DB_ENGINE=postgres und die LD_DB_*-Variablen schalten auf PostgreSQL um; für Teams bis zu einigen Dutzend Personen reicht SQLite.
  5. LD_DISABLE_URL_VALIDATION=True erlaubt 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 -f

Der 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.com

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

Anmeldung 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).zip

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

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

Bei 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

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

Danach 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

  1. Karakeep mit Docker installieren
  2. Linkwarden mit Docker installieren
  3. Nginx als Reverse Proxy mit TLS einrichten

Grundlagen dazu: Docker und Docker Compose auf Linux installieren, Docker Compose absichern.

Quellen

  1. sissbruecker/linkding auf GitHub
  2. Release v1.47.0
  3. linkding-Doku: Installation
  4. linkding-Doku: Backups
  5. linkding-Doku: Archivierung
  6. linkding-Doku: Optionen
  7. linkding-Doku: REST-API
  8. sissbruecker/linkding auf Docker Hub