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.

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:
| Eigenschaft | Wert | Herkunft der Angabe |
|---|---|---|
| Architekturen | 64-bit Intel, 64-bit ARM (Raspberry Pi, Apple Silicon), 32-bit ARM (ARMv7) | offizielle Dokumentation |
| Getestete Version | 2.11.0, Release vom 17.09.2026 | eigener Testlauf |
| Container-Port | 3000 | Dokumentation, im Test bestätigt |
| Datenordner im Container | /data | Dokumentation, im Test bestätigt |
| Arbeitsspeicher im Leerlauf | 5,469 MiB | eigene Messung mit docker stats |
| Zusätzliche Dienste | keine, ein einzelner Container ohne Datenbank | eigener Testlauf |
| Lizenz | MIT | GitHub-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:
imagelegt Registry, Image und Version fest. Die Version ist hier bewusst gepinnt.restart: unless-stoppedsorgt dafür, dass der Container nach einem Hostneustart oder einem Absturz automatisch wieder hochkommt, aber nicht, wenn er bewusst gestoppt wurde.environmententhältSB_USERim Formatbenutzer:passwort. Ohne diese Variable läuft der Dienst in einem anderen Bootmodus und ist nicht durch eine Anmeldung geschützt.volumesbindet den Hostordner./dataauf/dataim 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.portsverö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
/datagemounteten 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
silverbulletmit 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
PUIDundPGIDgezielt übersteuern, falls die automatische Erkennung nicht das gewünschte Ergebnis liefert. - Versionszeile. Die Zeile mit
SilverBullet 2.11.0-0-g3b669b81nennt 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, weilSB_USERgesetzt 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-shellzeigt, 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:3000lauscht. 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
| Aufruf | Beobachtete Antwort | Bedeutung |
|---|---|---|
| GET / ohne Anmeldung | HTTP 200, 2578 Byte, text/html | Anmelde- beziehungsweise Client-Seite wird ausgeliefert |
| GET /index ohne Anmeldung | HTTP 302 | Weiterleitung auf die Anmeldung, Inhalt ist geschützt |
| GET /.ping | HTTP 200 | geeignet für einfache Erreichbarkeitsprüfungen |
| docker compose ps nach Start | health: starting, danach healthy | eingebauter 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/
| Aufruf | Beobachtete Antwort | Einordnung |
|---|---|---|
| PUT /Testseite.md | HTTP 405 | kein API-Pfad, Methode nicht erlaubt |
| PUT /.fs/Test.md mit Sitzungscookie | HTTP 200 | Datei erschien sofort als data/Test.md auf dem Host |
| GET /.fs/ mit Sitzungscookie | HTTP 200, JSON-Liste | Felder name, created, lastModified, contentType, size und perm |
| PUT /.fs/Hack.md ohne Anmeldung | HTTP 401 | Schreibzugriff korrekt abgewiesen |
| GET /.fs/index.md ohne Anmeldung | HTTP 401 | Lesezugriff 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
| Datei | Rechte | Zweck |
|---|---|---|
| .silverbullet.auth.json | 600 | Zustand der Authentifizierung, unter anderem Sitzungsdaten |
| index.md | 644 | automatisch 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:3000dann- 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.jsonmuss ins Backup. Beitarauf 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.jsonzwingend 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
| Symptom | Ursache | Lösung |
|---|---|---|
| Container startet, aber Schreibzugriffe scheitern mit Rechtefehlern | Der Datenordner gehört einem anderen Benutzer als gewünscht, die automatische UID-Erkennung übernimmt diesen Wert | Besitzer 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 zugreifen | SB_USER ist nicht gesetzt, der Bootmodus ist dann nicht single-space | SB_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 405 | Falscher Pfad, es wurde direkt auf den Seitennamen geschrieben statt über die Datei-API | Pfadpräfix /.fs/ verwenden, also PUT auf /.fs/Seite.md |
| Monitoring meldet erfolgreiche Anmeldung, obwohl das Passwort falsch ist | Ein POST auf /.auth liefert auch bei falschem Passwort HTTP 200, die Seite enthält dann den Hinweis Invalid | Nicht nur den Statuscode prüfen, sondern auf ein gesetztes Sitzungscookie oder den erfolgreichen Abruf einer geschützten Seite testen |
| Container startet nicht, Fehlermeldung zum Port | Der gewählte Hostport ist bereits durch einen anderen Dienst belegt | Belegung 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 Domain | Zugriff außerhalb von localhost ohne TLS beziehungsweise fehlende oder falsche Proxy-Konfiguration | Reverse 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_BOOTzum 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_USERin 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
- TriliumNext mit Docker für das Wissensmanagement: Alternative mit eigener Datenhaltung, interessant zum Vergleich, wenn verschachtelte Notizbäume gebraucht werden.
- Memos mit Docker für Schnellnotizen: deutlich schlankerer Ansatz für kurze Notizen statt für eine gewachsene Wissensbasis.
- Traefik als Reverse Proxy mit HTTPS einrichten: der passende nächste Schritt, wenn SilverBullet über eine echte Domain mit TLS erreichbar sein soll.
Quellen
- GitHub: silverbulletmd/silverbullet: Repository, Lizenz, Sterne und Aktivität, Abruf 20.09.2026.
- SilverBullet-Dokumentation: Install/Docker: Image, Architekturen, Release-Kanäle, UID-Verhalten, TLS-Hinweis und Upgrade-Weg, Abruf 20.09.2026.
- SilverBullet: Projektseite und Dokumentation: Funktionsumfang, Space-Konzept und Space Lua, Abruf 20.09.2026.
- GitHub: SilverBullet Releases: Release 2.11.0 vom 17.09.2026, Abruf 20.09.2026.