Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Datenbanken 25.10.2026 · 10 min Lesezeit

rqlite mit Docker Compose: verteilte SQLite-Datenbank im Cluster betreiben

rqlite repliziert SQLite per Raft über mehrere Knoten. Die Anleitung zeigt Einzelknoten und Cluster mit Docker Compose, Authentifizierung per auth.json, Backup und echten Restore nach Totalverlust, das Update von 10.6.4 auf 10.6.5 sowie das Verhalten bei Knotenausfall.

Geprüft am 25.10.2026 · für rqlite 10.6.5

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * 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: rqlite im Cluster selbst betreiben, drei verbundene Datenbankknoten mit Leader-Markierung

rqlite legt eine Raft-Replikation vor SQLite: Jeder Schreibzugriff geht über den Leader, eine Mehrheit der Knoten bestätigt ihn, und fällt ein Rechner aus, übernimmt ein anderer. Das Go-Programm steht unter MIT-Lizenz und hat auf GitHub knapp 18.000 Sterne. Aktuell ist sie im Gespräch, weil Entwickler Philip O'Toole zum zwölften Geburtstag des Projekts einen vielbeachteten Rückblick auf Hacker News gebracht hat. Für kleine und mittlere Unternehmen ist rqlite interessant, wenn eine kleine relationale Datenbank ausfallsicher sein soll, ohne gleich einen PostgreSQL-Cluster mit Patroni zu betreiben.

Diese Anleitung zeigt einen abgesicherten Einzelknoten, Sicherung und Rücksicherung, das Update und einen Cluster aus drei Knoten mit Ausfalltest. Alle Ausgaben stammen aus unserem Testlauf mit rqlite 10.6.4 und 10.6.5.

Voraussetzungen

rqlite ist sparsam. Für einen Einzelknoten reichen 1 bis 2 CPU-Kerne und 1 GB RAM, der Bedarf wächst mit der Datenbank. Für echte Ausfallsicherheit gehören die drei Knoten auf drei getrennte Server oder VMs. Sie brauchen:

  • einen Linux-Server mit Docker Engine und Docker Compose Plugin, etwa Debian 12 oder Ubuntu 24.04, eingerichtet wie in unserer Grundlagen-Anleitung zu Docker Compose,
  • curl für die API-Aufrufe und optional sqlite3 zum Prüfen von Sicherungen,
  • für den Cluster drei Hosts, die sich über Port 4002 erreichen. Port 4002 darf nur zwischen den Knoten offen sein, Port 4001 nur für Ihre Anwendungen.
EckdatumWert
Imagerqlite/rqlite:10.6.5 (Docker Hub, Release vom 24.10.2026)
Ports4001 HTTP-API und Weboberfläche, 4002 Raft zwischen den Knoten
Volume/rqlite/file (Raft-Log, Snapshots, SQLite-Datei)
UmgebungsvariablenNODE_ID, HTTP_ADDR, RAFT_ADDR, optional SQLITE_EXTENSIONS
LizenzMIT

Schritt 1: Einzelknoten mit Authentifizierung anlegen

Die offiziellen Compose-Beispiele starten rqlite ohne Anmeldung und mit latest. Ohne -auth darf aber jeder im Netz Tabellen löschen, und latest macht Updates unkontrollierbar. Legen Sie daher einen Ordner /opt/rqlite mit drei Dateien an. Die .env legt die Version fest; für den späteren Update-Test beginnen wir bewusst eine Version zurück:

mkdir -p /opt/rqlite && cd /opt/rqlite
echo "RQLITE_TAG=10.6.4" > .env

Die compose.yaml bindet die API nur an 127.0.0.1. Den Raft-Port veröffentlicht ein Einzelknoten gar nicht, er wird nur im Cluster gebraucht:

services:
  rqlite:
    image: rqlite/rqlite:${RQLITE_TAG:-10.6.5}
    hostname: rqlite-1
    restart: unless-stopped
    command: -auth /config/auth.json
    environment:
      NODE_ID: rqlite-1
      HTTP_ADDR: rqlite-1:4001
      RAFT_ADDR: rqlite-1:4002
    volumes:
      - rqlite-data:/rqlite/file
      - ./auth.json:/config/auth.json:ro
    ports:
      - "127.0.0.1:4001:4001"
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://rqlite-1:4001/readyz"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

volumes:
  rqlite-data:

Das Entrypoint-Skript baut aus den Umgebungsvariablen die Startparameter und hängt die Optionen aus command an. Wie Sie Healthchecks und Secrets allgemein härten, zeigt unsere Anleitung zu Healthchecks, Secrets und Non-Root.

Die auth.json enthält Benutzer, Passwörter und Rechte. Erzeugen Sie die Passwörter zufällig, etwa mit openssl rand -base64 24, und setzen Sie die Datei auf chmod 600:

[
  {"username": "admin",  "password": "LANGES-ZUFALLSPASSWORT-1", "perms": ["all"]},
  {"username": "app",    "password": "LANGES-ZUFALLSPASSWORT-2", "perms": ["execute", "query"]},
  {"username": "backup", "password": "LANGES-ZUFALLSPASSWORT-3", "perms": ["backup"]},
  {"username": "*", "perms": ["status", "ready"]}
]

admin darf alles, die Anwendung nur execute und query, das Sicherungsskript nur backup. Der Sonderbenutzer * steht für anonyme Anfragen, hier nur Status und Bereitschaft für das Monitoring. Starten Sie den Dienst:

docker compose up -d
docker compose logs --no-log-prefix | grep "rqlited starting"
[rqlited] 2026/10/25 00:28:49 rqlited starting, version v10.6.4, SQLite 3.53.4, commit 64145717224d620809174cb2235ac86ec604aac5, compiler (toolchain) gc, compiler (command) musl-gcc

Verifizieren: docker compose ps zeigt den Dienst nach wenigen Sekunden als healthy. Ein anonymer Aufruf von curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4001/status liefert 200, weil * das Recht status hat. Ein Schreibversuch ohne Anmeldung muss dagegen 401 liefern.

Schritt 2: Daten über die HTTP-API schreiben und lesen

Schreibende Befehle gehen an /db/execute, Abfragen an /db/query, jeweils als JSON-Array. Werte übergeben Sie als Parameter, das schützt vor SQL-Injection. Wir legen eine Marker-Tabelle an, die später die Wiederherstellung beweist:

APP="app:LANGES-ZUFALLSPASSWORT-2"
curl -s -u "$APP" -XPOST 'http://127.0.0.1:4001/db/execute?pretty' \
  -H 'Content-Type: application/json' \
  -d '["CREATE TABLE marker (id INTEGER PRIMARY KEY, text TEXT, created TEXT)",
       ["INSERT INTO marker(text, created) VALUES(?, datetime())", "restore-test-2026-10-25"]]'
curl -s -u "$APP" -G 'http://127.0.0.1:4001/db/query' --data-urlencode 'q=SELECT * FROM marker'
{"results":[{"columns":["id","text","created"],"types":["integer","text","text"],"values":[[1,"restore-test-2026-10-25","2026-10-25 00:28:52"]]}]}

Wichtig für Skripte: SQL-Fehler stehen im Feld error, der HTTP-Status bleibt 200. Ein Insert in eine fehlende Tabelle lieferte bei uns "error":"no such table: gibtsnicht". Schreibende SQL-Befehle über /db/query lehnt rqlite ab, weil dieser Endpunkt eine reine Leseverbindung nutzt:

{"results":[{"error":"attempt to change database via query operation","error_v2":{"message":"attempt to write a readonly database","sqlite":{"code":8,"extended_code":8}}}]}

Die eingebaute Weboberfläche erreichen Sie unter /console/. Sie verlangt einen Benutzer mit dem Recht ui oder all, anonym kam 401. Vom Arbeitsplatz genügt ein SSH-Tunnel (ssh -L 4001:127.0.0.1:4001 server), dann öffnen Sie http://localhost:4001/console/.

rqlite-Konsole mit Clusterübersicht eines Einzelknotens, Version 10.6.5 als Leader
Startseite der Konsole: Knotenstatus, Version und Topologie auf einen Blick.
Query-Seite der rqlite-Konsole mit SELECT-Abfrage und Ergebnistabelle
Im Reiter Query führen Sie SQL aus und wählen die Lesekonsistenz.

Verifizieren: Die Abfrage liefert den Marker restore-test-2026-10-25, und die Konsole zeigt unter Schema die Tabelle marker mit drei Spalten.

Schritt 3: Rechte und Fehlermeldungen prüfen

Prüfen Sie die Rechte auch negativ. Im Test lieferten alle drei Fälle HTTP 401: Schreiben ohne Anmeldung, Abfrage mit falschem Passwort und ein Sicherungsabruf durch den Benutzer app, dem das Recht backup fehlt.

curl -s -o /dev/null -w '%{http_code}\n' -u admin:falsch 'http://127.0.0.1:4001/db/query?q=SELECT%201'
curl -s -o /dev/null -w '%{http_code}\n' -u "$APP" http://127.0.0.1:4001/db/backup
401
401

Verlässt der Verkehr den Server, aktivieren Sie HTTPS mit -http-cert und -http-key, denn Basic Auth überträgt Passwörter sonst im Klartext.

Verifizieren: Alle drei Aufrufe liefern 401, der korrekte Aufruf mit dem Benutzer app liefert 200.

Schritt 4: Sicherung erstellen

rqlite sichert im laufenden Betrieb. /db/backup liefert eine vollständige SQLite-Datei, mit ?fmt=sql einen SQL-Textdump. Der Benutzer backup genügt dafür:

mkdir -p /opt/rqlite/backup
curl -s -u "backup:LANGES-ZUFALLSPASSWORT-3" http://127.0.0.1:4001/db/backup \
  -o /opt/rqlite/backup/rqlite-$(date +%F).sqlite3 -w 'HTTP %{http_code} %{size_download} Bytes\n'
head -c 15 /opt/rqlite/backup/rqlite-*.sqlite3; echo
HTTP 200 8192 Bytes
SQLite format 3

Abbrüche meldet rqlite laut Dokumentation im Trailer-Header X-Stream-Error, prüfen Sie Sicherungen daher zusätzlich mit sqlite3 datei "PRAGMA integrity_check". Die Konsole bietet dieselbe Funktion unter Backup.

Backup-Seite der rqlite-Konsole mit Formatauswahl und Download-Schaltfläche
Sicherung per Konsole: SQLite-Datei, SQL-Dump, optional komprimiert.

Verifizieren: Die Datei beginnt mit SQLite format 3, und curl ... /db/backup?fmt=sql zeigt die Zeile INSERT INTO "marker" VALUES(1,'restore-test-2026-10-25',...).

Schritt 5: Update auf 10.6.5 einspielen

Mit festem Tag ist das Update eine Zeile in der .env, vorher sichern Sie wie in Schritt 4:

sed -i 's/RQLITE_TAG=10.6.4/RQLITE_TAG=10.6.5/' .env
docker compose pull && docker compose up -d
curl -s -u "admin:LANGES-ZUFALLSPASSWORT-1" http://127.0.0.1:4001/status | jq -r .build.version
v10.6.5

Der Container wurde neu erstellt, der Marker war danach sofort abrufbar. Im Cluster aktualisieren Sie die Knoten nacheinander und warten jeweils, bis /nodes wieder alle Knoten als erreichbar meldet.

Verifizieren: /status meldet v10.6.5, das Image-Label org.opencontainers.image.version ebenfalls, und die Marker-Abfrage liefert weiterhin eine Zeile.

Schritt 6: Wiederherstellung nach Totalverlust

Eine Sicherung ist erst etwas wert, wenn die Rücksicherung klappt. Wir haben den Ernstfall durchgespielt und das Volume komplett gelöscht:

docker compose down -v
docker compose up -d
curl -s -u "$APP" -G http://127.0.0.1:4001/db/query --data-urlencode 'q=SELECT text FROM marker'
{"results":[{"error":"no such table: marker", ...}]}

Die leere Datenbank laden Sie mit /db/load aus der SQLite-Datei. Das Recht load steckt in all, daher nutzen wir den Admin:

curl -s -u "admin:LANGES-ZUFALLSPASSWORT-1" -XPOST http://127.0.0.1:4001/db/load \
  -H 'Content-type: application/octet-stream' \
  --data-binary @/opt/rqlite/backup/rqlite-2026-10-25.sqlite3 -w ' HTTP %{http_code}\n'
curl -s -u "$APP" -G http://127.0.0.1:4001/db/query --data-urlencode 'q=SELECT * FROM marker'
{"results":[]} HTTP 200
{"results":[{"columns":["id","text","created"],...,"values":[[1,"restore-test-2026-10-25","2026-10-25 00:28:52"]]}]}

Der Marker samt Zeitstempel ist zurück. Laut Dokumentation eignet sich /db/load bis etwa 100 MB, größere Dateien spielen Sie über /boot in einen Einzelknoten ein.

Verifizieren: Vor dem Load meldet die Abfrage no such table: marker, danach liefert sie den ursprünglichen Datensatz mit unverändertem created-Wert.

Schritt 7: Cluster aus drei Knoten aufbauen

Raft verlangt eine Mehrheit: Bei drei Knoten müssen zwei laufen. Die folgende Datei startet drei Knoten auf einem Host zum Ausprobieren; in Produktion verteilen Sie sie auf drei Server mit erreichbaren Adressen. Knoten 2 und 3 treten mit -join bei und melden sich dabei als Benutzer cluster an:

x-rqlite: &rqlite
  image: rqlite/rqlite:${RQLITE_TAG:-10.6.5}
  restart: unless-stopped

services:
  rqlite-1:
    <<: *rqlite
    hostname: rqlite-1
    command: -auth /config/auth.json
    environment:
      NODE_ID: rqlite-1
      HTTP_ADDR: rqlite-1:4001
      RAFT_ADDR: rqlite-1:4002
    volumes:
      - ./auth.json:/config/auth.json:ro
      - data-1:/rqlite/file
    ports:
      - "127.0.0.1:4001:4001"
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://rqlite-1:4001/readyz?noleader"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  rqlite-2:
    <<: *rqlite
    hostname: rqlite-2
    command: -auth /config/auth.json -join rqlite-1:4002 -join-as cluster
    environment:
      NODE_ID: rqlite-2
      HTTP_ADDR: rqlite-2:4001
      RAFT_ADDR: rqlite-2:4002
    volumes:
      - ./auth.json:/config/auth.json:ro
      - data-2:/rqlite/file
    ports:
      - "127.0.0.1:4011:4001"
    depends_on:
      rqlite-1:
        condition: service_healthy

  rqlite-3:
    <<: *rqlite
    hostname: rqlite-3
    command: -auth /config/auth.json -join rqlite-1:4002 -join-as cluster
    environment:
      NODE_ID: rqlite-3
      HTTP_ADDR: rqlite-3:4001
      RAFT_ADDR: rqlite-3:4002
    volumes:
      - ./auth.json:/config/auth.json:ro
      - data-3:/rqlite/file
    ports:
      - "127.0.0.1:4021:4001"
    depends_on:
      rqlite-1:
        condition: service_healthy

volumes:
  data-1:
  data-2:
  data-3:

Die auth.json des Clusters braucht zusätzlich {"username": "cluster", "password": "...", "perms": ["join", "remove"]}. Der Healthcheck fragt /readyz?noleader ab, den Grund nennt das Troubleshooting.

Verifizieren: curl -s -u admin:... http://127.0.0.1:4001/nodes?pretty listet drei Knoten mit "voter": true und "reachable": true, einer davon mit "leader": true. In der Konsole zeigt die Topologie den Leader mit Stern.

rqlite-Konsole mit drei erreichbaren Knoten, rqlite-3 als Leader
Clusteransicht nach dem Kaltstart: drei Stimmberechtigte, Leader ist rqlite-3.

Schritt 8: Knotenausfall und Leader-Wechsel beobachten

Ein Schreibzugriff über den Follower 2 wurde an den Leader weitergeleitet. Danach haben wir den Leader rqlite-1 gestoppt. Gestoppt um 00:37:09.431, um 00:37:10.093 meldete rqlite-2 sich als neuer Leader. Schreibzugriffe über Knoten 2 und 3 liefen sofort weiter (HTTP 200). /nodes zeigte den fehlenden Knoten so:

"rqlite-1": {"voter": true, "reachable": false, "leader": false,
  "error": "dial tcp: lookup rqlite-1 on 127.0.0.11:53: server misbehaving"}

Anschließend haben wir den zweiten Knoten gestoppt. Der verbliebene Knoten hat kein Quorum mehr und verweigert Schreibzugriffe und Standard-Abfragen:

leader not found HTTP 503
[+]node ok
[+]leader does not exist HTTP 503

Mit level=none liest der Knoten lokal und lieferte alle drei Zeilen mit HTTP 200. Die Daten können dabei veraltet sein. Nach dem Neustart aller Knoten waren wieder drei Knoten erreichbar und alle Zeilen vorhanden.

Verifizieren: Nach dem Stopp eines Knotens zeigt /status unter store.leader.node_id einen neuen Leader, Inserts liefern 200. Bei zwei fehlenden Knoten antwortet /readyz mit 503.

Troubleshooting

MeldungUrsacheLösung
dependency failed to start: container ...-rqlite-1-1 is unhealthyHealthcheck auf /readyz: Nach Stopp aller Knoten wartet Knoten 1 auf einen Leader, Knoten 2 und 3 auf Knoten 1./readyz?noleader nutzen oder docker compose up -d --no-deps rqlite-2 rqlite-3
leader not found, HTTP 503Weniger als die Hälfte plus eins der Knoten läuftAusgefallene Knoten starten, für Notlesen level=none
HTTP 401Falsches Passwort oder fehlendes Recht in auth.jsonBenutzer und perms prüfen, Konsole braucht ui
attempt to change database via query operationSchreibender SQL-Befehl an /db/query/db/execute verwenden
no such table: ... mit HTTP 200SQL-Fehler stehen im JSON, nicht im StatuscodeFeld error in Skripten auswerten

Den Kaltstartfehler haben wir mit dem Healthcheck der offiziellen Beispieldatei selbst ausgelöst: /readyz meldete [+]leader does not exist mit HTTP 503, /readyz?noleader dagegen 200.

Häufige Fragen

Ersetzt rqlite PostgreSQL oder MariaDB?

Nein. rqlite passt zu kleinen bis mittleren Datenmengen, bei denen Ausfallsicherheit wichtiger ist als Schreibdurchsatz, denn jeder Schreibzugriff wartet auf die Mehrheit. Anwendungen sprechen HTTP oder nutzen Client-Bibliotheken.

Wie viele Knoten brauche ich?

Drei Knoten überstehen einen Ausfall, fünf zwei. Leseknoten ohne Stimmrecht hängen Sie als Read Replicas an.

Kann ich die Raft-Ports offen ins Netz stellen?

Nein. Port 4002 gehört nur zwischen die Knoten. Beschränken Sie ihn per Firewall, etwa mit ufw wie in unserer Anleitung zu ufw und Fail2ban. Für Verschlüsselung zwischen den Knoten bietet rqlite -node-cert und -node-key.

Gibt es automatische Sicherungen?

Ja. Mit -auto-backup lädt der Leader regelmäßig eine komprimierte Kopie in einen S3-kompatiblen Speicher oder ins Dateisystem. Alternativ rufen Sie /db/backup aus Schritt 4 per Cronjob auf.

Fazit

rqlite ist schnell eingerichtet und verhält sich im Ausfall wie versprochen: Der Leader-Wechsel dauerte im Test unter einer Sekunde, ohne Quorum gibt es klare 503-Meldungen. Sicherungen sind normale SQLite-Dateien. Achten Sie auf drei Punkte: Authentifizierung von Anfang an aktivieren, Versionen fest vorgeben und beim Cluster den Healthcheck mit noleader verwenden, sonst bleibt der Kaltstart hängen.

Weiterführende Anleitungen und Quellen

rqliteSQLiteDocker ComposeRaftDatenbankHochverfügbarkeitBackup