Deployment remoto privato
Questo modello esegue Libre WebUI, Ollama e Cloudflare Tunnel su un unico host Docker senza pubblicare le porte dell'applicazione o di Ollama. Cloudflare Access costituisce il confine esterno dell'identità; l'autenticazione di Libre WebUI resta il confine interno. Work e Watchtower sono opzioni distinte, equivalenti all'accesso root.
Questo modello usa la topologia solo con replica singola: SQLite, blob locali crittografati, vettori incorporati, coordinamento locale e un worker dei processi incorporato condividono il volume dei dati dell'applicazione. Non trasformarlo in un deployment per team modificando i selettori del backend in .env. I deployment per team devono usare docker-compose.team.yml del repository e, quando Work è abilitato, docker-compose.team.work.yml; tali file forniscono PostgreSQL/PGVector, archiviazione S3 con versioni, Redis, un worker esterno e il gateway come un'unica topologia coordinata.
Usa deploy/private/docker-compose.yml come punto di partenza. Per impostazione predefinita usa l'immagine main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
Il tag dev è adatto a un'istanza di sviluppo che lo seleziona esplicitamente, non al valore predefinito per i client.
Modello di sicurezza
- Cloudflare Access protegge l'intero nome host, compresi
/api/*e gli upgrade WebSocket. Non aggiungere percorsi pubblici che aggirino la protezione. - Libre WebUI richiede un account corrente per le API dell'applicazione. Il ciclo di vita dei modelli e le operazioni Work richiedono che il ruolo corrente nel database sia amministratore.
- L'applicazione, Ollama, SearXNG e cloudflared usano soltanto una rete Compose privata. L'host non pubblica porte dell'applicazione.
- Il servizio SearXNG incluso alimenta la ricerca web facoltativa. È accessibile soltanto internamente e resta inerte finché un amministratore non abilita la ricerca in Impostazioni > Ricerca; imposta
SEARXNG_SECRETin.envprima di avviare lo stack. - L'app viene eseguita senza privilegi root, con file system radice di sola lettura, nessuna funzionalità Linux, no-new-privileges e limiti per CPU, memoria e PID.
- Work è disabilitato a meno che non venga inclusa una delle relative sostituzioni. Quando è abilitato, i suoi container aggiungono un proprio file system radice di sola lettura, rimozione delle funzionalità, limiti delle risorse, volume dello spazio di lavoro e criteri di rete con negazione predefinita.
Lo stack di base non monta alcun socket Docker. Abilitare Work con docker-compose.work-proxy.yml mantiene tale proprietà: un proxy del socket su una rete interna detiene il socket e inoltra soltanto le sezioni dell'API usate da Work (container, immagini, volumi, reti, exec e info); gli endpoint swarm, segreti, build e sistema vengono negati dal proxy e l'applicazione non necessita del mount del socket né dell'appartenenza al relativo gruppo. Il proxy riduce la superficie dell'API Docker, ma non il raggio d'impatto di ciò che inoltra: chi può creare container può comunque montare percorsi dell'host. Consideralo quindi un vero livello di protezione, non un isolamento multi-tenant.
Le alternative con socket non filtrato restano il confine di fiducia più ampio: le sostituzioni docker-compose.work.yml e Watchtower concedono a un processo nel container la possibilità di effettuare chiamate arbitrarie all'API Docker, che possono controllare l'host. Montare un socket in sola lettura non rende di sola lettura l'accesso all'API Docker. L'helper di backup integrato rifiuta di ereditare un socket Docker non filtrato; migra Work al proxy filtrato prima di affidarti ai backup integrati pianificati.
Avvio iniziale
- Crea un operatore sudo non root e verifica l'accesso SSH con chiave prima di disabilitare l'accesso SSH di root.
- Copia
deploy/private/.env.examplein/opt/libre-webui/.env, imposta la modalità0600, genera segreti univoci e dimensionaBLOB_QUOTA_BYTES_PER_USERper l'host.BLOB_QUOTA_RESERVATION_TTL_MSfa scadere le prenotazioni di caricamento abbandonate; il valore predefinito è un'ora. - Se Work verrà abilitato, imposta
DOCKER_GIDsul gruppo numerico proprietario di/var/run/docker.sock. - Archivia il token del tunnel Cloudflare in
/opt/libre-webui/secrets/tunnel-tokencon modalità0640o più restrittiva. - Crea un'applicazione self-hosted Cloudflare Access per l'intero nome host, usa una sessione di 24 ore e consenti soltanto le identità previste. Abilita Protect with Access sulla route del tunnel. Se il monitoraggio richiede un controllo di integrità pubblico, crea un'applicazione o criterio separato limitato esclusivamente al percorso
/health/live. Non aggiungere mai un criterio Bypass generale all'applicazione principale: i criteri Bypass corrispondenti annullano il criterio Allow. - Mantieni
ENABLE_SIGNUP=false. Quando l'elenco consentito di Access protegge il nome host, crea il primo amministratore locale; un database vuoto consente automaticamente quell'unico account di avvio. Abilita la registrazione soltanto durante una finestra successiva e intenzionale. - Configura le restrizioni di Turnstile per il nome host e imposta
TURNSTILE_EXPECTED_HOSTNAMEsul nome host pubblico esatto.
Avvia e verifica:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Per abilitare Work, includi intenzionalmente la sostituzione del proxy del socket:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
La variante con socket non filtrato (docker-compose.work.yml) resta disponibile per i deployment che ne hanno bisogno, con le conseguenze sul rapporto di fiducia descritte sopra.
Quando Access è attivo, i test rapidi dalla riga di comando richiedono un token di servizio Cloudflare Access, a meno che il percorso esatto non disponga di una deroga limitata. Archivia le credenziali fuori dalla cronologia della shell e invia entrambe le intestazioni:
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
Una richiesta non autenticata a un'API dell'applicazione protetta deve restituire 401:
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
Protezione dell'host
La directory include un file drop-in di sshd e una jail fail2ban. Prima di applicare il drop-in di sshd, verifica una sessione sudo non root distinta in un altro terminale. Controlla la configurazione con sshd -t prima di ricaricare SSH.
Usa UFW, o un firewall equivalente, per negare per impostazione predefinita il traffico in ingresso e consentire soltanto SSH con limite di frequenza. In questo modello Docker non pubblica porte dei servizi:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Mantieni abilitati gli aggiornamenti di sicurezza automatici. Disabilita l'inoltro X11, agent e TCP, a meno che il deployment non ne abbia una necessità documentata.
Backup e ripristino
Prima di creare un backup, esegui l'inventario di ripristino di sola lettura nel container del deployment in esecuzione. In questo modo usi esattamente la versione dell'applicazione distribuita, il relativo ambiente e il volume dei dati montato. Un comando eseguito da un checkout sull'host potrebbe esaminare il database errato o eseguire codice sorgente diverso dall'immagine distribuita.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
Lo stato di uscita 0 indica che non sono stati trovati elementi che impediscono la preparazione al ripristino, 1 indica che il report JSON contiene elementi bloccanti e 2 indica che non è stato possibile eseguire il comando. Il report include soltanto un'impronta digitale della chiave di crittografia e indicatori sulla presenza dei segreti; non stampa mai una chiave o il valore di un altro segreto. Conserva l'inventario con il backup corrispondente, così gli operatori possono confrontare versione dell'applicazione, impronta digitale dello schema, risorse Work previste ed esclusioni prima di un ripristino.
Crea chiavi dedicate per crittografia e firma del backup usando esattamente l'immagine distribuita. Mantieni questa directory fuori dal volume dell'applicazione e copia la chiave di crittografia e la chiave privata di firma in una posizione di ripristino protetta distinta:
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
La generazione delle chiavi rifiuta i file di output esistenti. Non generare mai nuove chiavi sopra un set di backup esistente: la perdita della chiave di crittografia dell'archivio o dell'identità di firma rende inutilizzabile la relativa prova di ripristino.
Installa gli script di backup e ripristino forniti e le unità systemd, quindi abilita il 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
L'unità legge facoltativamente le sostituzioni destinate esclusivamente alla manutenzione da /etc/libre-webui/backup.env; non carica il file .env dell'applicazione. Crea il file come root soltanto quando serve una sostituzione:
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 e LIBRE_WEBUI_BACKUP_KEY_DIR possono essere impostati direttamente al suo interno. Mantieni il file di proprietà di root e con modalità 0600. Una directory delle chiavi personalizzata deve restare leggibile da root nella sandbox systemd.
La modifica di LIBRE_WEBUI_BACKUP_DIR cambia anche il confine di scrittura di systemd. La directory deve esistere prima dell'avvio del servizio e l'unità necessita di un drop-in corrispondente. Ad esempio, dopo aver impostato 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
Aggiungi questo percorso esatto nell'editor, quindi ricarica l'unità:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Senza la voce ReadWritePaths= corrispondente, ProtectSystem=strict impedisce correttamente al timer di scrivere in una posizione personalizzata.
Il servizio di backup concede fino a sei ore per gli archivi di grandi dimensioni. L'helper acquisisce un lock dell'host, arresta l'applicazione soltanto se era già in esecuzione e crea l'archivio sul volume inattivo usando esattamente l'immagine distribuita. L'archivio contiene un manifesto firmato e un payload crittografato dall'operatore; include la directory dei dati e la configurazione di runtime e dei segreti necessaria per aprire tale stato. L'helper verifica poi in modo indipendente l'archivio completo prima di pubblicare atomicamente il relativo report dei metadati. I container di manutenzione di sola lettura ricevono un tmpfs /tmp privato e scrivibile per esaminare SQLite e verificare l'archivio autenticato; nel livello del container non viene conservato testo normale temporaneo. Copia fuori dall'host entrambi i file e le chiavi di ripristino protette separatamente.
Quando Work usa docker-compose.work-proxy.yml, il ripristino deve inoltre provare che ogni volume Work a cui fa riferimento il database esista ancora. L'helper legge il valore DOCKER_HOST dell'applicazione distribuita, individua il servizio proxy del socket nello stesso progetto Compose attivo e rileva l'unica rete interna condivisa dai relativi collegamenti di rete Docker effettivi. Compose antepone a tale rete il nome del progetto, quindi non configurare né codificare un nome di rete presunto. Soltanto il container che crea l'archivio si collega a tale rete interna e può raggiungere il proxy filtrato; non riceve un socket non filtrato. La verifica indipendente dell'archivio continua con --network none. L'assenza del proxy, un endpoint imprevisto, una rete condivisa esterna o ambigua oppure un mount del socket non filtrato causa un errore prima che l'applicazione venga arrestata e prima che venga pubblicato un archivio.
Prova il ripristino in un nuovo volume senza sostituire quello attivo:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
L'helper di ripristino rifiuta un volume o una destinazione di configurazione esistente, verifica l'archivio e il relativo inventario di ripristino interno in uno spazio di archiviazione usa e getta, quindi copia i dati nel nuovo volume e scrive i file runtime.json e secrets.json recuperati con autorizzazioni private. Non riconfigura né avvia mai lo stack attivo. Esamina la configurazione recuperata, aggiorna intenzionalmente i valori specifici del deployment e prova il volume ripristinato con uno stack isolato.
I modelli Ollama possono essere scaricati di nuovo. I volumi Docker di Work, i PVC Kubernetes di Work e le cartelle Work associate all'host si trovano fuori dalla directory dei dati dell'applicazione e richiedono snapshot coordinati e criteri di conservazione propri.
Aggiornamenti
Libre WebUI conserva uno stato anche quando il tag dell'immagine è modificabile. Il file Compose di base etichetta permanentemente l'applicazione come esclusa da Watchtower. Aggiornala soltanto come azione coordinata dell'operatore:
- Registra l'ID dell'immagine in esecuzione e risolvi la sostituzione esaminata in un digest immutabile.
- Esegui
libre-webui recovery-check, avvia il servizio di backup e richiedi la creazione di un nuovo archivio e del relativo report di verifica prima di continuare. - Imposta
LIBRE_WEBUI_IMAGEsul digest esaminato, scaricalo e ricrea soltantolibre-webuicon Docker Compose. Non rimuovere né ricreare il relativo volume dei dati. - Richiedi il superamento dei test rapidi per
/health/ready, accesso, sessione/cronologia, recupero dei documenti e Work. In caso contrario, ripristina il digest dell'immagine registrato; conserva per la diagnosi sia lo stato non riuscito sia il backup verificato.
La sequenza lato host è intenzionalmente manuale. Sostituisci il digest soltanto dopo averlo esaminato e controlla la coppia .lwb e .json più recente prima del download:
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}}'
La sostituzione facoltativa Watchtower con accesso al socket resta disponibile soltanto per i sidecar esplicitamente etichettati nel file di base:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower controlla Ollama e SearXNG ogni 30 minuti. I dati dei modelli Ollama restano nel relativo volume denominato e la configurazione di SearXNG nel relativo mount associato. Non aggiorna Libre WebUI, cloudflared, il proxy del socket Work o le sandbox Work. Un deployment client segue main; un'istanza sperimentale può selezionare :dev, ma l'applicazione richiede comunque lo stesso aggiornamento manuale subordinato al backup. Non collegare mai questo stack privato individuale ai servizi di persistenza per team; distribuisci invece la topologia completa per team.