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

SilverBullet 2.11.0 mit Docker Compose: private Markdown-Wissensbasis ohne Datenbank

SilverBullet legt Notizen als reine Markdown-Dateien in einem Ordner ab, ganz ohne Datenbank. Diese Anleitung zeigt die Installation von Version 2.11.0 mit Docker Compose in einer tatsächlich getesteten Umgebung: gepinntes Image, UID-Übernahme aus dem Datenordner, eingebauter Healthcheck, geprüfte HTTP-Antworten der Datei-API, Backup samt bestätigter Wiederherstellung sowie die Stolperfallen, die in keiner Dokumentation stehen.

Illustration zur Einrichtung einer eigenen Wissensbasis mit SilverBullet und Docker: Markdown-Dateien, eigener Zugang und Backup als Ordner KI-generiert

SilverBullet ist eine selbst gehostete Notiz- und Wissensplattform, die einen entscheidenden Unterschied zu den meisten Alternativen macht: Die Inhalte liegen nicht in einer Datenbank, sondern als gewöhnliche Markdown-Dateien in einem Ordner. Dieser Ordner heißt im Projekt "Space". Wer ein Backup zieht, kopiert schlicht Textdateien. Wer migrieren will, nimmt den Ordner mit. Wer das Werkzeug irgendwann nicht mehr mag, kann die Notizen trotzdem weiter lesen.

Diese Anleitung beschreibt die Installation von SilverBullet 2.11.0 mit Docker Compose. Der beschriebene Aufbau wurde am 20.09.2026 in einer isolierten Testumgebung tatsächlich ausgeführt: Ubuntu mit Docker 29.1.3 und Compose 2.40.3, Image auf die feste Version gepinnt, Hostport 3999. Alle Logzeilen, HTTP-Statuscodes und Messwerte in diesem Text stammen aus diesem Lauf. Abschnitte, die ausschließlich auf der offiziellen Dokumentation beruhen und hier nicht praktisch geprüft wurden, sind jeweils ausdrücklich gekennzeichnet.

Wofür SilverBullet gut ist und wo die Grenzen liegen

Laut Projektbeschreibung im Repository handelt es sich um eine "open source personal productivity platform built on Markdown, turbo charged with the scripting power of Lua". Der Client ist in TypeScript auf Basis von CodeMirror 6 gebaut, das Server-Backend in Rust. Der Server liefert das vorgebaute Client-Bundle aus, stellt die Datei- und Space-HTTP-API bereit, übernimmt die Authentifizierung und bringt eine Headless-Chrome-Laufzeit für serverseitiges Lua mit.

Der praktische Kern ist die Dateiablage. Es gibt keine proprietäre Datenbank für die Notizen selbst. Das hat sehr konkrete Folgen für den Betrieb:

  • Backup ist trivial. Ein Archiv des Datenordners genügt, es braucht keinen Datenbank-Dump und keinen konsistenten Snapshot über mehrere Dienste hinweg.
  • Migration ist trivial. Der Ordner lässt sich auf einen anderen Host kopieren, dort mountet ihn der nächste Container einfach wieder.
  • Kein Vendor-Lock-in. Die Dateien sind mit jedem Texteditor lesbar, auch wenn der Dienst gar nicht läuft.
  • Versionierung ist einfach nachrüstbar. Ein Git-Repository über dem Datenordner funktioniert, weil es sich um Textdateien handelt.

Ebenso ehrlich gehören die Grenzen benannt. SilverBullet bringt im hier beschriebenen Standardbetrieb kein Mehrbenutzer-Rechtesystem im klassischen Sinn mit. Die Variable SB_USER definiert genau einen Zugang, und der Bootmodus heißt entsprechend "single-space". Es gibt in diesem Aufbau keine Rollen, keine Gruppen, keine Freigabeprozesse und keine seitenbezogenen Berechtigungen. Wer ein Firmenwiki mit Redaktionsworkflow, Leserechten pro Abteilung und Freigabe durch Vorgesetzte braucht, ist hier falsch. Und ohne vorgeschaltetes TLS gehört der Dienst nicht ins Netz, dazu später mehr.

Abgrenzung zu anderen Notiz- und Wiki-Werkzeugen

Auf S-EDV gibt es bereits Anleitungen zu anderen Notiz- und Wissenswerkzeugen. Die Auswahl hängt weniger am Funktionsumfang als an der Betriebsform. SilverBullet ist die bessere Wahl, wenn der Zustand des Systems ein Ordner mit Markdown-Dateien sein soll, wenn eigene Kommandos, Seitenvorlagen und Widgets per Space Lua gewünscht sind und wenn der Ressourcenbedarf sehr klein bleiben muss, etwa auf einem Raspberry Pi oder einem kleinen vServer mit wenig Arbeitsspeicher. SilverBullet ist die schlechtere Wahl, wenn mehrere Personen mit unterschiedlichen Rechten arbeiten sollen oder wenn klassische Dokumentenfreigabe im Team gebraucht wird. Dann passen Werkzeuge mit echtem Benutzer- und Rollenmodell besser. Weitergehende Funktionsvergleiche sparen wir uns an dieser Stelle bewusst, weil sie ohne eigenen Parallelbetrieb nicht belastbar wären.

Voraussetzungen und Ressourcenbedarf

Gebraucht werden ein Linux-Host mit Docker und dem Compose-Plugin sowie ein freier Port. Die Testumgebung lief mit Docker 29.1.3 und Compose 2.40.3. Das Image kommt von der GitHub Container Registry unter ghcr.io/silverbulletmd/silverbullet und basiert laut Dokumentation auf Alpine. Der Container lauscht intern auf Port 3000, der Datenordner im Container ist /data.

Die folgende Tabelle fasst die harten Eckdaten zusammen und nennt jeweils, woher die Angabe stammt:

EigenschaftWertHerkunft der Angabe
Architekturen64-bit Intel, 64-bit ARM (Raspberry Pi, Apple Silicon), 32-bit ARM (ARMv7)offizielle Dokumentation
Getestete Version2.11.0, Release vom 17.09.2026eigener Testlauf
Container-Port3000Dokumentation, im Test bestätigt
Datenordner im Container/dataDokumentation, im Test bestätigt
Arbeitsspeicher im Leerlauf5,469 MiBeigene Messung mit docker stats
Zusätzliche Dienstekeine, ein einzelner Container ohne Datenbankeigener Testlauf
LizenzMITGitHub-API, Abruf 20.09.2026

Der gemessene Leerlaufverbrauch von 5,469 MiB stammt aus docker stats --no-stream auf der frisch gestarteten Instanz. Das ist ein außergewöhnlich niedriger Wert für einen Dienst dieser Art und macht SilverBullet auch für einen kleinen Server oder einen Raspberry Pi tauglich. Unter Last steigt der Verbrauch natürlich, insbesondere sobald die Chromium-Laufzeit für serverseitiges Lua tatsächlich arbeitet.

Die Dokumentation nennt zusätzlich eine schlankere Image-Variante ohne Chromium. Dafür wird an den Tag -slim angehängt, also etwa :2.11.0-slim oder :latest-slim. Diese Variante spart Platz und Speicher, verzichtet dafür aber auf die Runtime-API für serverseitiges Lua. Die slim-Variante wurde hier nicht selbst getestet, die Angabe stammt allein aus der Dokumentation.

Die compose.yml Schritt für Schritt

Das offizielle Beispiel der Dokumentation verwendet den Tag :latest. Für einen Serverbetrieb ist das die schlechtere Wahl. Ein beweglicher Tag bedeutet, dass ein beliebiger docker compose pull unangekündigt eine andere Version holt. Mit einer festen Versionsnummer ist dagegen jederzeit nachvollziehbar, welcher Stand läuft, ein Update wird zu einer bewussten Änderung an einer Datei, und dieselbe compose.yml erzeugt auf jedem Host denselben Zustand. Genau so wurde hier getestet.

services:
  silverbullet:
    # Feste Version statt :latest, damit Updates reproduzierbar bleiben
    image: ghcr.io/silverbulletmd/silverbullet:2.11.0
    # Container startet nach Hostneustart und nach Absturz wieder
    restart: unless-stopped
    environment:
      # Format benutzer:passwort - hier ein Platzhalter, bitte ersetzen
      - SB_USER=admin:BitteHierEinStarkesPasswort
    volumes:
      # Bind-Mount: der Space liegt als normaler Ordner auf dem Host
      - ./data:/data
    ports:
      # Hostport 3999 auf den Containerport 3000
      - 3999:3000

Die einzelnen Zeilen im Detail:

  • image legt Registry, Image und Version fest. Die Version ist hier bewusst gepinnt.
  • restart: unless-stopped sorgt dafür, dass der Container nach einem Hostneustart oder einem Absturz automatisch wieder hochkommt, aber nicht, wenn er bewusst gestoppt wurde.
  • environment enthält SB_USER im Format benutzer:passwort. Ohne diese Variable läuft der Dienst in einem anderen Bootmodus und ist nicht durch eine Anmeldung geschützt.
  • volumes bindet den Hostordner ./data auf /data im Container. Ein Bind-Mount statt eines Named Volume ist hier ein bewusster Vorteil, denn die Markdown-Dateien liegen damit direkt und sichtbar im Dateisystem des Hosts.
  • ports veröffentlicht den internen Port 3000 auf dem Hostport 3999. Die Portwahl ist frei, hier wurde 3999 genommen, um Konflikte mit anderen Diensten zu vermeiden.

Wer das Passwort nicht ausgeschrieben in der compose.yml stehen haben möchte, kann es in eine .env-Datei neben der compose.yml auslagern:

# Datei .env neben der compose.yml, nicht ins Git-Repository aufnehmen
SB_USER=admin:BitteHierEinStarkesPasswort

In der compose.yml steht dann statt der ausgeschriebenen Zeile nur noch - SB_USER=${SB_USER}. Diese Variante wurde hier nicht separat getestet, getestet wurde die oben gezeigte compose.yml mit direkt gesetztem Wert.

Installation und erster Start

Die folgenden Schritte wurden in dieser Reihenfolge tatsächlich ausgeführt.

# Projektordner anlegen und hineinwechseln
mkdir -p ~/silverbullet
cd ~/silverbullet

# Datenordner vorab anlegen, damit die UID-Erkennung greift
mkdir -p data

# compose.yml aus dem vorherigen Abschnitt in diesem Ordner speichern,
# danach den Stack im Hintergrund starten
docker compose up -d

# Status der Container anzeigen
docker compose ps

# Startausgaben des Dienstes ansehen
docker compose logs

Der Datenordner sollte vor dem ersten Start angelegt werden und dem Benutzer gehören, unter dem die Daten später verwaltet werden sollen. Der Grund steht im nächsten Abschnitt.

Was beim ersten Start passiert

Die folgenden Zeilen stammen unverändert aus docker compose logs des Testlaufs:

Will run SilverBullet with UID 1000, inferred from the owner of /data (set PUID environment variable to override)
Creating 'silverbullet' group (with GID 1000) and 'silverbullet' user (with UID 1000) inside container
Running SilverBullet as user configured with PUID 1000 and PGID 1000
INFO silverbullet::server: SilverBullet 2.11.0-0-g3b669b81-2026-09-17T13-06-11Z
INFO silverbullet::server: Data folder: /data
INFO silverbullet::boot: boot mode: single-space (SB_USER is set)
INFO silverbullet_server_runtime_chrome::config: runtime Chrome detected: /usr/bin/chromium-headless-shell
INFO silverbullet::server: SilverBullet is now running: http://0.0.0.0:3000

Diese Ausgabe verdient eine genauere Betrachtung, weil sie einen der größten Praxisvorteile dieses Images zeigt:

  • UID-Erkennung. Der Startcode liest Besitzer und Gruppe des nach /data gemounteten Ordners aus und übernimmt diese Werte. Im Test war das UID 1000, also der Host-Benutzer, dem der Projektordner gehört.
  • Anlegen von Gruppe und Benutzer. Im Container werden daraufhin eine Gruppe und ein Benutzer silverbullet mit genau dieser GID und UID erzeugt.
  • Kein Rechte-Gefrickel. Das erspart die sonst übliche Nacharbeit mit chown, weil der Dienst mit derselben UID schreibt wie der Besitzer des Datenordners. Dateien, die der Container anlegt, gehören anschließend dem Host-Benutzer und sind von ihm direkt les- und bearbeitbar.
  • Überschreibbar. Laut Dokumentation lässt sich dieses Verhalten mit den Umgebungsvariablen PUID und PGID gezielt übersteuern, falls die automatische Erkennung nicht das gewünschte Ergebnis liefert.
  • Versionszeile. Die Zeile mit SilverBullet 2.11.0-0-g3b669b81 nennt die tatsächlich laufende Version samt Commit-Hash und Buildzeitpunkt. Das ist die verlässlichste Quelle für die laufende Version, verlässlicher als der Image-Tag.
  • Bootmodus. boot mode: single-space (SB_USER is set) bestätigt, dass die Anmeldung aktiv ist, weil SB_USER gesetzt wurde. Fehlt die Variable, meldet der Dienst einen anderen Modus, und damit gibt es keinen Anmeldeschutz.
  • Chromium-Laufzeit. runtime Chrome detected: /usr/bin/chromium-headless-shell zeigt, dass das Standard-Image die Laufzeit für serverseitiges Lua mitbringt. Bei der slim-Variante wäre dieser Fund nicht zu erwarten.
  • Startmeldung. Die letzte Zeile bestätigt, dass der Dienst im Container auf 0.0.0.0:3000 lauscht. Das ist die Container-Sicht, nach außen gilt der Hostport aus der compose.yml.

Funktionsprüfung und Healthcheck

Das Image bringt einen eigenen Healthcheck mit, es muss also keiner in der compose.yml nachgerüstet werden. Die Definition lässt sich am laufenden Container auslesen:

# Healthcheck-Definition des laufenden Containers anzeigen
docker inspect --format '{{json .Config.Healthcheck}}' silverbullet-silverbullet-1

Im Test lieferte das folgende Prüfung zurück:

CMD-SHELL curl --fail "http://localhost:$SB_PORT/.instance" || exit 1

In docker compose ps stand direkt nach dem Start zunächst health: starting, kurz darauf healthy. Wer automatisiert auf den Start wartet, sollte also diesen Zustand abfragen und nicht blind ein paar Sekunden warten.

Zusätzlich wurden diese HTTP-Aufrufe ausgeführt und die Antworten notiert:

# Startseite ohne Anmeldung, liefert die Anmeldeseite
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3999/

# Eine Inhaltsseite ohne Anmeldung
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3999/index

# Einfacher Erreichbarkeitsendpunkt
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3999/.ping
AufrufBeobachtete AntwortBedeutung
GET / ohne AnmeldungHTTP 200, 2578 Byte, text/htmlAnmelde- beziehungsweise Client-Seite wird ausgeliefert
GET /index ohne AnmeldungHTTP 302Weiterleitung auf die Anmeldung, Inhalt ist geschützt
GET /.pingHTTP 200geeignet für einfache Erreichbarkeitsprüfungen
docker compose ps nach Starthealth: starting, danach healthyeingebauter Healthcheck greift

Wichtig für Monitoring: Ein HTTP 200 auf / bedeutet nur, dass der Webserver antwortet. Es ist ausdrücklich kein Beleg dafür, dass jemand angemeldet ist oder dass Inhalte erreichbar sind. Für ein reines Erreichbarkeitssignal eignet sich /.ping, für eine Zustandsprüfung der eingebaute Healthcheck.

Anmeldung und Zugangsschutz

Der Zugangsschutz läuft über die Umgebungsvariable SB_USER im Format benutzer:passwort. Die Anmeldung erfolgt über einen POST auf /.auth mit den Feldern username und password. Im Test wurde eine Sitzung so erzeugt:

# Anmelden und Sitzungscookie in eine Datei schreiben
curl -s -c cookies.txt -o /dev/null -w "%{http_code}\n" \
  -X POST \
  -d "username=admin" \
  -d "password=BitteHierEinStarkesPasswort" \
  http://localhost:3999/.auth

# Mit dem Cookie eine geschuetzte Seite abrufen
curl -s -b cookies.txt -o /dev/null -w "%{http_code}\n" http://localhost:3999/index

Mit korrektem Passwort lieferte der POST HTTP 200 und setzte ein Sitzungscookie, der anschließende Abruf von /index lieferte ebenfalls HTTP 200.

Praxishinweis aus dem Test, der so in keiner Dokumentation steht: Eine Anmeldung mit falschem Passwort liefert ebenfalls HTTP 200. Die Antwortseite enthält in diesem Fall den Hinweis Invalid, und es wird keine Sitzung vergeben. Der Zugang ist also korrekt geschützt, aber der Statuscode allein unterscheidet Erfolg und Fehlschlag nicht. Wer für Monitoring oder Skripte nur auf HTTP 200 prüft, bekommt ein falsches Bild und hält eine fehlgeschlagene Anmeldung für erfolgreich. Zuverlässig ist stattdessen die Prüfung, ob tatsächlich ein Sitzungscookie gesetzt wurde, oder ob ein anschließender Abruf einer geschützten Seite ohne Weiterleitung gelingt.

Für das Passwort selbst gelten die üblichen Regeln: lang, zufällig, nur an dieser Stelle verwendet. Da es sich um genau einen Zugang für den gesamten Space handelt, ist es der einzige Schutz vor dem Zugriff auf sämtliche Notizen.

Die Datei-API für Automatisierung und Backup

Für Skripte, Importe und Auswertungen ist die Datei-API interessant. Sie liegt unter dem Pfadpräfix /.fs/. Ein häufiger Irrtum ist, Dateien direkt unter ihrem Seitenpfad schreiben zu wollen. Das funktioniert nicht, wie der Test zeigt.

# Falscher Pfad: direkt auf den Seitennamen schreiben
curl -s -o /dev/null -w "%{http_code}\n" \
  -X PUT --data-binary "Inhalt" \
  http://localhost:3999/Testseite.md

# Richtiger Pfad: ueber das Praefix /.fs/ mit Sitzungscookie
curl -s -b cookies.txt -o /dev/null -w "%{http_code}\n" \
  -X PUT --data-binary "Inhalt" \
  http://localhost:3999/.fs/Test.md

# Dateiliste des Space als JSON abrufen
curl -s -b cookies.txt http://localhost:3999/.fs/
AufrufBeobachtete AntwortEinordnung
PUT /Testseite.mdHTTP 405kein API-Pfad, Methode nicht erlaubt
PUT /.fs/Test.md mit SitzungscookieHTTP 200Datei erschien sofort als data/Test.md auf dem Host
GET /.fs/ mit SitzungscookieHTTP 200, JSON-ListeFelder name, created, lastModified, contentType, size und perm
PUT /.fs/Hack.md ohne AnmeldungHTTP 401Schreibzugriff korrekt abgewiesen
GET /.fs/index.md ohne AnmeldungHTTP 401Lesezugriff korrekt abgewiesen

Zwei Dinge sind daran wichtig. Erstens: Der Endpunkt ist korrekt geschützt, unangemeldete Zugriffe scheitern mit HTTP 401, und zwar lesend wie schreibend. Zweitens: Die API ist eine brauchbare Grundlage für Automatisierung. Ein Skript kann Seiten anlegen, die Dateiliste mit Zeitstempeln und Größen auslesen und damit etwa Importe aus anderen Systemen durchführen. Für Backups ist der direkte Dateizugriff auf dem Host allerdings der einfachere Weg.

Dass eine per API geschriebene Datei unmittelbar als data/Test.md im Hostordner auftauchte, ist die praktische Bestätigung dafür, dass zwischen API und Dateisystem tatsächlich keine Datenbankschicht liegt.

Persistente Daten und Rechte

Nach dem ersten Start enthielt der Ordner ./data im Test genau zwei Einträge:

# Inhalt des Datenordners samt Rechten und Besitzer anzeigen
ls -la data
DateiRechteZweck
.silverbullet.auth.json600Zustand der Authentifizierung, unter anderem Sitzungsdaten
index.md644automatisch angelegte Startseite des Space

Beide Dateien gehörten dem Host-Benutzer mit UID 1000. Die UID-Übernahme aus dem vorherigen Abschnitt funktioniert also wie dokumentiert, und die Dateien lassen sich vom Host aus ohne erhöhte Rechte lesen und bearbeiten.

Die Datei .silverbullet.auth.json verdient besondere Aufmerksamkeit. Sie hält den Authentifizierungszustand der Instanz und ist deshalb mit den restriktiven Rechten 600 angelegt, also nur für den Besitzer les- und schreibbar. Diese Datei gehört nicht in ein öffentliches Git-Repository und nicht in einen Ordner, der über einen Dateisync in fremde Hände geraten kann. Wer den Datenordner versioniert, sollte sie über .gitignore ausschließen. Für ein vollständiges Backup muss sie dagegen mit gesichert werden, weil sie bei einem ls ohne -a unsichtbar bleibt und sonst leicht vergessen wird.

Sichere Netzwerkfreigabe und Reverse Proxy mit TLS

Dieser Abschnitt wurde nicht praktisch getestet. Der Testlauf fand ausschließlich lokal auf localhost:3999 statt, ohne Domain, ohne Zertifikat und ohne vorgeschalteten Proxy. Die folgenden Punkte stammen aus der offiziellen Dokumentation und aus allgemeiner Betriebspraxis.

Die Dokumentation warnt ausdrücklich, dass für einen Zugriff außerhalb von localhost TLS eingerichtet werden muss. Das ist keine Formalie: Ohne Verschlüsselung wandern Benutzername, Passwort und sämtliche Notizinhalte im Klartext durch das Netz. Daraus ergibt sich ein klares Vorgehen:

  • Den Containerport niemals direkt ins Internet veröffentlichen. Zwischen Internet und Container gehört ein Reverse Proxy, der das Zertifikat terminiert.
  • Sobald ein Reverse Proxy auf demselben Host läuft, den Hostport nur an die Loopback-Adresse binden. In der compose.yml wird aus - 3999:3000 dann - 127.0.0.1:3999:3000. Damit ist der Dienst von außen nicht mehr direkt erreichbar, sondern ausschließlich über den Proxy.
  • Alternativ den Container in ein gemeinsames Docker-Netzwerk mit dem Proxy hängen und die Portveröffentlichung ganz weglassen.
  • Am Proxy HTTP auf HTTPS umleiten und moderne Verschlüsselungseinstellungen verwenden.
  • Wer zusätzlichen Schutz möchte, kann dem Dienst am Proxy eine weitere Authentifizierungsschicht vorschalten. Das ist angesichts des Einzelbenutzer-Modells eine naheliegende Ergänzung.

Backup und Wiederherstellung

Hier liegt die größte Stärke dieses Aufbaus. Der komplette Zustand der Instanz ist ein Ordner mit Textdateien. Es gibt keinen Datenbankdienst, der vorher in einen konsistenten Zustand gebracht werden müsste. Beide folgenden Schritte wurden im Test tatsächlich ausgeführt.

# In den Projektordner wechseln
cd ~/silverbullet

# Backup des kompletten Datenordners erstellen
tar czf sb-backup.tgz -C ~/silverbullet data

# Inhalt des Archivs kontrollieren, die versteckte Auth-Datei muss dabei sein
tar tzf sb-backup.tgz

Für den Wiederherstellungstest wurde anschließend gezielt eine Datei gelöscht und aus dem Archiv zurückgeholt:

# Eine Datei absichtlich entfernen
rm ~/silverbullet/data/Test.md

# Aus dem Archiv wiederherstellen
tar xzf sb-backup.tgz -C ~/silverbullet

# Ergebnis pruefen, die Datei muss wieder vorhanden sein
ls -la ~/silverbullet/data

Im Test erzeugte der erste Befehl ein 1,1 KB großes Archiv der frischen Instanz. Nach dem Löschen und dem anschließenden Entpacken war die Datei wieder vorhanden. Die Wiederherstellung ist damit praktisch bestätigt und nicht bloß behauptet.

Ergänzende Hinweise:

  • Die versteckte Datei .silverbullet.auth.json muss ins Backup. Bei tar auf den ganzen Ordner ist sie automatisch enthalten, bei selbst gebauten Dateilisten wird sie gern vergessen.
  • Ein einfacher Dateisync auf ein anderes System funktioniert ebenfalls, weil es sich um gewöhnliche Dateien handelt. Dann sollte die Auth-Datei je nach Ziel ausgenommen werden.
  • Ein Git-Repository über dem Datenordner ist möglich und liefert zusätzlich eine Versionshistorie. In diesem Fall gehört .silverbullet.auth.json zwingend in die .gitignore.
  • Für ein Backup im laufenden Betrieb ist das Risiko gering, aber nicht null: Wird genau während des Archivierens eine Seite geschrieben, kann diese eine Datei unvollständig im Archiv landen. Wer ganz sicher gehen will, stoppt den Container für die Dauer des Archivierens.

Updates und Rollback-Grenzen

Der offizielle Weg laut Dokumentation besteht darin, die Version in der compose.yml anzupassen und danach drei Befehle auszuführen:

# Vor jedem Update: Backup des Datenordners erstellen
tar czf sb-backup-vor-update.tgz -C ~/silverbullet data

# Danach die Versionsnummer in der compose.yml anpassen
# und das neue Image herunterladen
docker compose pull

# Laufenden Container stoppen
docker compose stop

# Mit dem neuen Image wieder starten
docker compose up -d

# Laufende Version in den Logs gegenpruefen
docker compose logs | grep "SilverBullet 2."

Zu den Release-Kanälen: :latest zeigt auf das jeweils letzte Release, :edge auf den letzten Commit im Hauptzweig. Der Alt-Tag :v2 verweist auf dasselbe Image wie :latest. Für produktive Nutzung ist :edge ungeeignet, weil dort ungetesteter Entwicklungsstand landen kann und sich der Inhalt des Tags jederzeit ändert. Sinnvoll ist eine feste Versionsnummer wie im Beispiel oben.

Ein Rollback ist bei SilverBullet vergleichsweise unkritisch, weil die Inhalte reine Markdown-Dateien sind und es keine Datenbankmigration gibt, die sich nicht zurückdrehen ließe. Trotzdem gilt: Ein Backup vor dem Update ist Pflicht, denn ein neuer Stand kann Hilfsdateien im Space verändern. Ein echtes Versions-Upgrade von einer älteren auf eine neuere Version und ein Downgrade wurden hier nicht getestet, der Testlauf startete direkt mit 2.11.0.

Typische Fehler mit Diagnose und Lösung

SymptomUrsacheLösung
Container startet, aber Schreibzugriffe scheitern mit RechtefehlernDer Datenordner gehört einem anderen Benutzer als gewünscht, die automatische UID-Erkennung übernimmt diesen WertBesitzer des Ordners korrigieren oder PUID und PGID in der compose.yml explizit setzen und den Container neu starten
Jeder kann ohne Anmeldung auf die Notizen zugreifenSB_USER ist nicht gesetzt, der Bootmodus ist dann nicht single-spaceSB_USER=benutzer:passwort ergänzen und prüfen, ob im Log boot mode: single-space (SB_USER is set) erscheint
Skript bekommt beim Schreiben einer Seite HTTP 405Falscher Pfad, es wurde direkt auf den Seitennamen geschrieben statt über die Datei-APIPfadpräfix /.fs/ verwenden, also PUT auf /.fs/Seite.md
Monitoring meldet erfolgreiche Anmeldung, obwohl das Passwort falsch istEin POST auf /.auth liefert auch bei falschem Passwort HTTP 200, die Seite enthält dann den Hinweis InvalidNicht nur den Statuscode prüfen, sondern auf ein gesetztes Sitzungscookie oder den erfolgreichen Abruf einer geschützten Seite testen
Container startet nicht, Fehlermeldung zum PortDer gewählte Hostport ist bereits durch einen anderen Dienst belegtBelegung mit ss -tlnp prüfen und in der compose.yml einen freien Hostport eintragen, der Containerport 3000 bleibt unverändert
Anmeldung funktioniert lokal, aber nicht über die DomainZugriff außerhalb von localhost ohne TLS beziehungsweise fehlende oder falsche Proxy-KonfigurationReverse Proxy mit gültigem Zertifikat vorschalten, Hostport auf 127.0.0.1 binden

Für die Diagnose sind zwei Befehle fast immer der richtige Einstieg:

# Laufzustand und Healthcheck-Status aller Container des Stacks
docker compose ps

# Startausgaben und laufende Meldungen des Dienstes
docker compose logs --tail 50

Die Dokumentation erwähnt außerdem eine Seite CONTAINER_BOOT im Space, deren Inhalt beim Containerstart als Bash-Skript ausgeführt wird, typischerweise um mit apk add zusätzliche Alpine-Pakete nachzuinstallieren. Diese Funktion wurde hier nicht getestet. Sicherheitlich ist sie nicht harmlos: Wer schreibenden Zugriff auf den Space hat, kann damit Befehle im Container ausführen.

Saubere Deinstallation

Zum Entfernen genügen zwei Befehle im Projektordner:

# In den Projektordner wechseln
cd ~/silverbullet

# Container stoppen und entfernen
docker compose down

# Optional: das heruntergeladene Image entfernen
docker image rm ghcr.io/silverbulletmd/silverbullet:2.11.0

Weil in dieser Anleitung ein Bind-Mount auf ./data verwendet wird und kein Named Volume, bleiben die Notizen als ganz normale Dateien auf dem Host erhalten. Genau das ist ein handfester Vorteil gegenüber Named Volumes: Es gibt keinen Volume-Namen, den man beim Aufräumen versehentlich mit docker volume prune oder docker compose down -v mitlöscht, und der Verbleib der Daten ist im Dateisystem direkt sichtbar.

Warnung vor Datenverlust: Der Ordner ./data enthält den gesamten Space. Wird er gelöscht, sind sämtliche Notizen weg, und es gibt keine zweite Kopie in einer Datenbank oder in einem Volume. Vor dem Löschen des Ordners zwingend ein Archiv nach dem oben gezeigten Verfahren erstellen und dieses Archiv auf einem anderen Datenträger ablegen.

Was in dieser Umgebung nicht geprüft wurde

Der Vollständigkeit halber die Liste der Punkte, die in dieser Anleitung aus der offiziellen Dokumentation stammen und im Testlauf nicht praktisch nachvollzogen wurden:

  • Betrieb hinter einem Reverse Proxy mit TLS und echter Domain
  • Die slim-Variante des Images ohne Chromium
  • Die Seite CONTAINER_BOOT zum Nachinstallieren von Alpine-Paketen
  • Ein echtes Versions-Upgrade von einer älteren auf eine neuere Version sowie ein Downgrade
  • Mehrbenutzerbetrieb und Anmeldung über OIDC
  • Serverseitiges Lua über die Runtime-API
  • Die Auslagerung von SB_USER in eine .env-Datei

Alles Übrige, insbesondere Start, Logausgaben, Healthcheck, HTTP-Statuscodes, Datei-API, Dateirechte, Speicherverbrauch sowie Backup und Wiederherstellung, wurde am 20.09.2026 in der beschriebenen Umgebung ausgeführt und beobachtet.

Einordnung und Projektlage

Zur Bewertung der Pflege des Projekts, alle Angaben laut GitHub-API mit Abruf am 20.09.2026: Das Repository silverbulletmd/silverbullet hat 6.098 Sterne, der letzte Push erfolgte am 19.09.2026, das Repository ist nicht archiviert, die Lizenz ist MIT. Das aktuelle Release ist 2.11.0 vom 17.09.2026. Ein Release zwei Tage vor dem Abruf und ein Push vom Vortag sprechen für ein aktiv gepflegtes Projekt.

Für kleine und mittlere Unternehmen ist SilverBullet interessant als persönliche oder abteilungsinterne Wissensablage mit sehr kleinem Betriebsaufwand: ein Container, kein Datenbankdienst, wenige Megabyte Arbeitsspeicher im Leerlauf, Backup durch Kopieren eines Ordners. Für ein gemeinsames Firmenwiki mit Rollen, Freigaben und getrennten Bereichen ist es im hier beschriebenen Aufbau nicht das passende Werkzeug.

Passende Anleitungen auf S-EDV

Quellen

SilverBulletDockerDocker ComposeMarkdownWissensmanagementSelfhostingBackup