Zum Hauptinhalt springen

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_SECRET in .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

  1. Erstelle einen sudo-Betreiber ohne root und prüfe schlüsselbasiertes SSH, bevor du root-SSH deaktivierst.
  2. Kopiere deploy/private/.env.example nach /opt/libre-webui/.env, setze 0600, erzeuge eindeutige Geheimnisse und dimensioniere BLOB_QUOTA_BYTES_PER_USER. BLOB_QUOTA_RESERVATION_TTL_MS lässt verlassene Reservierungen nach standardmäßig einer Stunde verfallen.
  3. Setze bei Work DOCKER_GID auf die numerische Gruppe von /var/run/docker.sock.
  4. Speichere das Tunnel-Token unter /opt/libre-webui/secrets/tunnel-token mit 0640 oder strenger.
  5. 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/live beschränkte Richtlinie. Eine allgemeine Bypass-Richtlinie besiegt Allow.
  6. 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.
  7. Konfiguriere Turnstile-Hostbeschränkung und setze TURNSTILE_EXPECTED_HOSTNAME exakt.

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:

  1. Notiere Image-ID und löse den geprüften Ersatz in einen unveränderlichen Digest auf.
  2. Führe libre-webui recovery-check aus, starte Sicherung und verlange neues Archiv und Bericht.
  3. Setze LIBRE_WEBUI_IMAGE auf den Digest, lade und erstelle nur libre-webui neu. Lösche oder erstelle das Datenvolume nicht.
  4. 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.