Private Remote-Bereitstellung
Dieses Muster führt Libre WebUI, Ollama und Cloudflare Tunnel auf einem Docker-Host aus, ohne Anwendungs- oder Ollama-Ports zu veröffentlichen. Cloudflare Access ist die äußere Identitätsgrenze, Libre WebUI-Authentifizierung die innere. Work und Watchtower sind getrennte Optionen mit root-gleichen Rechten.
Die Vorlage ist die solo-Topologie mit einer Replik: SQLite, lokale verschlüsselte
Blobs, eingebettete Vektoren, lokale Koordination und eingebetteter Job-Worker teilen
das Datenvolume. Verwandle sie nicht durch Backend-Selektoren in .env in eine
Team-Bereitstellung. Teams müssen docker-compose.team.yml (und bei Work
docker-compose.team.work.yml) verwenden; diese stellen PostgreSQL/PGVector,
versioniertes S3, Redis, externen Worker und Gateway gemeinsam bereit.
Ausgangspunkt ist
deploy/private/docker-compose.yml
mit dem Standard-Image main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
dev eignet sich nur für ausdrücklich gewählte Entwicklungsinstanzen.
Sicherheitsmodell
- Cloudflare Access schützt den gesamten Hostnamen einschließlich
/api/*und WebSocket-Upgrades. Füge keine öffentlichen Umgehungspfade hinzu. - Libre WebUI verlangt aktuelle Konten; Modelllebenszyklus und Work erfordern die aktuelle Administratorrolle.
- Anwendung, Ollama, SearXNG und cloudflared nutzen nur ein privates Compose-Netz. Der Host veröffentlicht keine Anwendungsports.
- SearXNG ermöglicht optionale Websuche, ist intern und bleibt
inaktiv bis zur Administratoraktivierung. Setze
SEARXNG_SECRETin.env. - Die Anwendung läuft ohne root, mit schreibgeschütztem Root-Dateisystem, ohne Linux- Fähigkeiten, mit no-new-privileges und CPU-/Speicher-/PID-Limits.
- Work ist ohne Überschreibung deaktiviert. Aktivierte Container ergänzen eigene schreibgeschützte Wurzel, Fähigkeitsentzug, Limits, Volume und standardmäßig verweigertes Netzwerk.
Der Basisstack mountet keinen Docker-Socket. docker-compose.work-proxy.yml behält
das bei: Ein interner Proxy hält den Socket und leitet nur Container, Images, Volumes,
Netze, exec und Info weiter; swarm, Geheimnisse, Build und System werden verweigert.
Die Anwendung braucht weder Mount noch Gruppe. Der Proxy verkleinert die API, nicht
den möglichen Schaden: Wer Container erstellt, kann Hostpfade mounten. Er ist
Härtung, keine Mehrmandantenisolation.
Ungefilterte Alternativen sind die größte Vertrauensgrenze:
docker-compose.work.yml und Watchtower erlauben beliebige Docker-Aufrufe. Ein
schreibgeschützter Socket-Mount macht die API nicht schreibgeschützt. Der integrierte
Sicherungshelfer verweigert ungefilterte Sockets; migriere vor geplanten Sicherungen.
Ersteinrichtung
- Erstelle einen sudo-Betreiber ohne root und prüfe schlüsselbasiertes SSH, bevor du root-SSH deaktivierst.
- Kopiere
deploy/private/.env.examplenach/opt/libre-webui/.env, setze0600, erzeuge eindeutige Geheimnisse und dimensioniereBLOB_QUOTA_BYTES_PER_USER.BLOB_QUOTA_RESERVATION_TTL_MSlässt verlassene Reservierungen nach standardmäßig einer Stunde verfallen. - Setze bei Work
DOCKER_GIDauf die numerische Gruppe von/var/run/docker.sock. - Speichere das Tunnel-Token unter
/opt/libre-webui/secrets/tunnel-tokenmit0640oder strenger. - Erstelle eine selbst gehostete Cloudflare Access-Anwendung für den vollständigen
Hostnamen, 24-Stunden-Sitzung und nur beabsichtigte Identitäten. Aktiviere
Protect with Access. Für öffentliche Zustandsprüfung verwende eine getrennte,
auf
/health/livebeschränkte Richtlinie. Eine allgemeine Bypass-Richtlinie besiegt Allow. - Behalte
ENABLE_SIGNUP=false. Erstelle nach Schutz des Hostnamens den ersten lokalen Administrator; eine leere Datenbank erlaubt genau dieses Startkonto. Öffne Registrierung später nur bewusst. - Konfiguriere Turnstile-Hostbeschränkung und setze
TURNSTILE_EXPECTED_HOSTNAMEexakt.
Start und Prüfung:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Work bewusst mit Proxy aktivieren:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
Die ungefilterte Variante (docker-compose.work.yml) bleibt mit genannten Folgen.
Nach Aktivierung benötigen CLI-Tests ein Cloudflare Access-Diensttoken, sofern keine enge Ausnahme existiert. Bewahre Daten außerhalb des Shellverlaufs auf:
curl --fail --silent --show-error \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/auth/system-info
Eine nicht authentifizierte geschützte Anfrage muss 401 liefern:
curl --output /dev/null --write-out '%{http_code}\n' \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/work/tasks
Hosthärtung
Das Verzeichnis enthält sshd-Include und fail2ban-Jail. Prüfe vor Anwendung eine
separate sudo-Sitzung ohne root und teste mit sshd -t vor dem Neuladen.
Nutze UFW oder gleichwertig mit standardmäßiger Ablehnung und rate-limitiertem SSH. Docker veröffentlicht in dieser Vorlage keine Dienstports:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Halte unbeaufsichtigte Sicherheitsupdates aktiv. Deaktiviere X11-, Agent- und TCP- Weiterleitung ohne dokumentierten Bedarf.
Sicherung und Wiederherstellung
Führe vor einer Sicherung das schreibgeschützte Inventar im laufenden Container aus. So nutzt es exakt Version, Umgebung und gemountetes Volume; ein Host-Checkout könnte andere Daten oder anderen Code prüfen.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
Status 0 bedeutet keine Blocker, 1 Blocker im JSON, 2 keine Ausführung. Der
Bericht enthält nur Schlüsselfingerabdruck und Geheimnis-Präsenz, nie Werte. Bewahre
ihn mit der Sicherung für Versions-, Schema-, Work- und Ausschlussvergleich auf.
Erzeuge eigene Verschlüsselungs- und Signaturschlüssel mit dem exakten Image. Halte das Verzeichnis außerhalb des Volumes und kopiere die Schlüssel geschützt:
install -d -m 0700 /etc/libre-webui/backup-keys
image_ref=$(docker inspect libre-webui --format '{{.Image}}')
docker run --rm --user 0:0 --read-only --network none --cap-drop ALL \
--security-opt no-new-privileges \
--mount type=bind,src=/etc/libre-webui/backup-keys,dst=/backup-keys \
--entrypoint /usr/local/bin/libre-webui "$image_ref" \
backup keygen \
--directory /backup-keys
Vorhandene Ausgaben werden abgelehnt. Erzeuge nie neue Schlüssel über vorhandenen Sätzen; der Verlust eines Schlüssels macht den Nachweis unbrauchbar.
Installiere Skripte und systemd-Einheiten und aktiviere den Timer:
install -d -m 0700 /var/backups/libre-webui
install -m 0750 deploy/private/libre-webui-backup \
/usr/local/sbin/libre-webui-backup
install -m 0750 deploy/private/libre-webui-restore \
/usr/local/sbin/libre-webui-restore
install -m 0644 deploy/private/libre-webui-backup.{service,timer} \
/etc/systemd/system/
systemctl daemon-reload
systemctl enable --now libre-webui-backup.timer
Die Einheit liest optional /etc/libre-webui/backup.env, nicht Anwendungs-.env.
Erstelle es nur bei Bedarf als root:
install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env
LIBRE_WEBUI_STACK_DIR, LIBRE_WEBUI_BACKUP_RETENTION_DAYS,
LIBRE_WEBUI_CONTAINER_NAME und LIBRE_WEBUI_BACKUP_KEY_DIR können dort stehen.
Datei bleibt root und 0600; benutzerdefinierte Schlüsselverzeichnisse müssen im
systemd-Sandbox lesbar sein.
LIBRE_WEBUI_BACKUP_DIR ändert auch die Schreibgrenze. Das Verzeichnis muss
existieren und die Einheit ein passendes Include haben. Nach
LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui in backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
Füge den exakten Pfad hinzu und lade neu:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Ohne ReadWritePaths= verhindert ProtectSystem=strict korrekt das Schreiben.
Der Dienst erlaubt bis zu sechs Stunden. Der Helfer sperrt den Host, stoppt die
Anwendung nur, wenn sie lief, und erstellt mit exaktem Image ein Archiv vom ruhenden
Volume. Es enthält signiertes Manifest, betreiberverschlüsselte Nutzlast, Daten und
nötige Konfiguration. Danach wird es unabhängig vollständig geprüft und atomar
veröffentlicht. Schreibgeschützte Wartungscontainer erhalten ein privates /tmp-
tmpfs; temporärer Klartext bleibt nicht in der Containerschicht. Kopiere Dateien und
Schlüssel vom Host.
Bei docker-compose.work-proxy.yml muss die Wiederherstellung alle referenzierten
Volumes nachweisen. Der Helfer liest DOCKER_HOST, findet den Proxy im selben
Compose-Projekt und entdeckt ihr einziges gemeinsames internes Netz anhand echter
Anhänge. Compose präfixiert den Namen; kodiere keinen vermuteten. Nur der
Archivcontainer tritt bei und erhält keinen Rohsocket. Unabhängige Prüfung nutzt
--network none. Fehlender Proxy, unerwarteter Endpunkt, externes/mehrdeutiges Netz
oder Rohsocket bricht vor Stopp und Veröffentlichung ab.
Teste in ein neues Volume:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
Der Helfer lehnt vorhandene Ziele ab, prüft Archiv und Inventar in Wegwerfspeicher,
kopiert Daten und schreibt runtime.json und secrets.json privat. Er verdrahtet
oder startet den Live-Stack nie. Prüfe Konfiguration und teste isoliert.
Ollama-Modelle können neu geladen werden. Work-Volumes, Kubernetes-PVCs und Hostordner liegen außerhalb und brauchen koordinierte Snapshots und Aufbewahrung.
Aktualisierungen
Libre WebUI ist zustandsbehaftet, auch bei veränderlichem Image-Tag. Compose schließt die Anwendung dauerhaft von Watchtower aus. Aktualisiere nur koordiniert:
- Notiere Image-ID und löse den geprüften Ersatz in einen unveränderlichen Digest auf.
- Führe
libre-webui recovery-checkaus, starte Sicherung und verlange neues Archiv und Bericht. - Setze
LIBRE_WEBUI_IMAGEauf den Digest, lade und erstelle nurlibre-webuineu. Lösche oder erstelle das Datenvolume nicht. - Verlange
/health/ready, Anmeldung, Sitzung/Verlauf, Dokumentabruf und Work-Tests. Rolle bei Fehler zurück und bewahre Fehlerzustand und Sicherung auf.
Die Hostsequenz ist bewusst manuell. Prüfe Digest sowie neuestes .lwb/.json vor
dem Abruf:
docker inspect libre-webui --format '{{.Config.Image}} {{.Image}}'
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
systemctl start libre-webui-backup.service
systemctl --no-pager --full status libre-webui-backup.service
ls -lt /var/backups/libre-webui/libre-webui-integrated-* | head
# Set LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui@sha256:REVIEWED_DIGEST
# in the root-owned .env, then recreate only the application.
docker compose pull libre-webui
docker compose up -d --no-deps libre-webui
docker inspect libre-webui --format '{{.State.Health.Status}} {{.Image}}'
Watchtower mit Socket bleibt nur für ausdrücklich markierte Sidecars:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower prüft Ollama und SearXNG alle 30 Minuten. Modelldaten und Konfiguration
bleiben in ihren Volumes/Mounts. Libre WebUI, cloudflared, Proxy und Work-Sandboxes
werden nicht aktualisiert. Kunden folgen main, Experimente dürfen :dev wählen,
benötigen aber dieselbe sicherungsgebundene manuelle Aktualisierung. Verbinde diesen
privaten Einzelstack nie mit Team-Persistenzdiensten; stelle die vollständige
Teamtopologie bereit.