Despliegue remoto privado
Este patrón ejecuta Libre WebUI, Ollama y Cloudflare Tunnel en un host Docker sin publicar los puertos de la aplicación ni de Ollama. Cloudflare Access es el límite de identidad exterior; la autenticación de Libre WebUI permanece como límite interior. Work y Watchtower son opciones separadas, equivalentes a root.
La plantilla corresponde a la topología solo de una réplica: SQLite, blobs locales
cifrados, vectores integrados, coordinación local y un worker integrado comparten el
volumen de datos. No la conviertas en un despliegue de equipo cambiando selectores
del backend en .env. Los despliegues de equipo deben usar
docker-compose.team.yml (y docker-compose.team.work.yml con Work), que provisiona
PostgreSQL/PGVector, almacenamiento S3 versionado, Redis, un worker externo y la
puerta como una topología coordinada.
Usa
deploy/private/docker-compose.yml
como punto de partida. Usa la imagen main por defecto:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
La etiqueta dev sirve para una instancia de desarrollo elegida expresamente, no
para el cliente predeterminado.
Modelo de seguridad
- Cloudflare Access protege todo el nombre de host, incluido
/api/*y las actualizaciones WebSocket. No añadas rutas públicas de omisión. - Libre WebUI exige una cuenta actual para las API. Las operaciones de modelos y Work requieren que el rol actual sea administrador.
- La aplicación, Ollama, SearXNG y cloudflared solo usan una red Compose privada. El host no publica puertos de la aplicación.
- El servicio SearXNG integrado impulsa la búsqueda web opcional. Es
interno e inerte hasta que un administrador la habilita; define
SEARXNG_SECRETen.envantes de iniciar la pila. - La aplicación se ejecuta sin root, con raíz de solo lectura, sin capacidades Linux, no-new-privileges y límites de CPU, memoria y PID.
- Work está desactivado salvo que se incluya una de sus sustituciones. Al activarlo, sus contenedores añaden raíz de solo lectura, retirada de capacidades, límites, volumen de trabajo y política de red que deniega por defecto.
La pila base no monta ningún socket. Activar Work con
docker-compose.work-proxy.yml lo mantiene así: un proxy interno conserva el socket
y solo remite las secciones usadas (contenedores, imágenes, volúmenes, redes, exec e
información); swarm, secretos, compilación y sistema se deniegan, y la aplicación no
necesita montar el socket ni pertenecer a su grupo. El proxy reduce la superficie de
la API, no el alcance de daño de lo que permite: quien crea contenedores aún puede
montar rutas del host. Trátalo como refuerzo real, no como aislamiento multiinquilino.
Las alternativas de socket sin filtrar siguen siendo el mayor límite de confianza:
docker-compose.work.yml y Watchtower permiten llamadas arbitrarias a Docker que
pueden controlar el host. Un montaje de solo lectura no hace que la API sea de solo
lectura. El asistente de copias integrado se niega a heredar un socket sin filtrar;
migra Work al proxy antes de confiar en copias programadas.
Preparación inicial
- Crea un operador sudo sin root y verifica el acceso SSH por clave antes de desactivar SSH para root.
- Copia
deploy/private/.env.examplea/opt/libre-webui/.env, establece el modo0600, genera secretos únicos y dimensionaBLOB_QUOTA_BYTES_PER_USER.BLOB_QUOTA_RESERVATION_TTL_MShace caducar reservas abandonadas; el valor predeterminado es una hora. - Si habilitarás Work, define
DOCKER_GIDcomo el grupo numérico propietario de/var/run/docker.sock. - Guarda el token del túnel en
/opt/libre-webui/secrets/tunnel-tokencon modo0640o más estricto. - Crea una aplicación autoalojada de Cloudflare Access para el nombre completo,
usa una sesión de 24 horas y permite solo las identidades previstas. Activa
Protect with Access en la ruta. Si la supervisión requiere un control público,
crea una política separada limitada a
/health/live. Nunca añadas una política Bypass general: las coincidencias derrotan la política Allow. - Mantén
ENABLE_SIGNUP=false. Una vez protegido el host, crea el primer administrador local; una base vacía permite automáticamente esa única cuenta inicial. Habilita el registro solo durante una ventana deliberada posterior. - Configura las restricciones de Turnstile y define
TURNSTILE_EXPECTED_HOSTNAMEcomo el nombre público exacto.
Inicia y verifica:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Para activar Work, incluye expresamente la sustitución del proxy:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
La variante con socket sin filtrar (docker-compose.work.yml) sigue disponible con
las consecuencias de confianza descritas.
Cuando Access esté activo, las pruebas por línea de comandos requieren un token de servicio salvo que la ruta tenga una omisión estrecha. Guarda las credenciales fuera del historial y envía ambas cabeceras:
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 solicitud no autenticada a una API protegida debe devolver 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
Refuerzo del host
El directorio contiene una inclusión de sshd y una cárcel de fail2ban. Antes de
aplicarla, verifica otra sesión sudo sin root en una terminal distinta. Prueba la
configuración con sshd -t antes de recargar SSH.
Usa UFW o un cortafuegos equivalente para denegar por defecto y permitir solo SSH con velocidad limitada. Docker no publica puertos de servicio en esta plantilla:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Mantén activadas las actualizaciones de seguridad desatendidas. Desactiva el reenvío X11, de agente y TCP salvo necesidad documentada.
Copias de seguridad y recuperación
Antes de copiar, ejecuta el inventario de recuperación de solo lectura dentro del contenedor desplegado. Así usa exactamente la versión, entorno y volumen montado. Un comando desde un checkout del host podría inspeccionar otra base o código distinto.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
El estado 0 indica que no hay bloqueos, 1 que el informe JSON contiene bloqueos
y 2 que el comando no pudo ejecutarse. El informe solo incluye la huella de la
clave y señales de presencia de secretos; nunca imprime valores. Consérvalo con la
copia para comparar versión, huella del esquema, recursos Work esperados y exclusiones.
Crea claves de cifrado y firma dedicadas con la imagen exacta. Mantén el directorio fuera del volumen y copia la clave de cifrado y la privada de firma a otro lugar:
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 generación rechaza archivos existentes. Nunca generes claves nuevas sobre un conjunto existente: perder la clave del archivo o la identidad de firma inutiliza la prueba de recuperación.
Instala los scripts y unidades systemd y activa el temporizador:
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
La unidad puede leer sustituciones exclusivas de mantenimiento desde
/etc/libre-webui/backup.env; no carga .env de la aplicación. Crea el archivo
como root solo si hace falta:
install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env
Se pueden definir LIBRE_WEBUI_STACK_DIR, LIBRE_WEBUI_BACKUP_RETENTION_DAYS,
LIBRE_WEBUI_CONTAINER_NAME y LIBRE_WEBUI_BACKUP_KEY_DIR. Mantén el archivo como
root y modo 0600. Un directorio personalizado debe ser legible por root dentro del
aislamiento systemd.
Cambiar LIBRE_WEBUI_BACKUP_DIR también cambia el límite de escritura de systemd.
El directorio debe existir y la unidad necesita una inclusión coincidente. Tras
definir LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui en backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
Añade esta ruta exacta y recarga:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Sin ReadWritePaths= coincidente, ProtectSystem=strict impide correctamente
escribir en la ubicación personalizada.
El servicio permite hasta seis horas. El asistente obtiene un bloqueo del host,
detiene la aplicación solo si ya estaba activa y crea el archivo sobre el volumen en
reposo con la imagen exacta. El archivo incluye un manifiesto firmado, carga cifrada,
el directorio y la configuración necesaria. Luego verifica independientemente todo
antes de publicar el informe de forma atómica. Los contenedores de mantenimiento de
solo lectura reciben un tmpfs /tmp privado para SQLite y la verificación; no se
conserva texto sin cifrar en la capa. Copia ambos archivos y las claves fuera del host.
Cuando Work usa docker-compose.work-proxy.yml, la recuperación debe probar que
existe cada volumen referido por la base. El asistente lee DOCKER_HOST, localiza el
proxy del mismo proyecto y descubre su única red interna compartida a partir de las
conexiones reales. Compose antepone el nombre del proyecto: no codifiques un nombre
supuesto. Solo el contenedor creador se une a esa red y alcanza el proxy; no recibe
socket sin filtrar. La verificación independiente usa --network none. La ausencia
del proxy, un endpoint inesperado, una red externa o ambigua o un montaje sin filtrar
fallan antes de detener la aplicación o publicar el archivo.
Prueba la recuperación en un volumen nuevo sin sustituir el activo:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
El asistente rechaza un volumen o destino existente, verifica el archivo y su
inventario en almacenamiento desechable, copia los datos y escribe runtime.json y
secrets.json con permisos privados. Nunca reconecta ni inicia la pila activa.
Inspecciona la configuración, actualiza deliberadamente valores específicos y prueba
el volumen con una pila aislada.
Los modelos Ollama pueden descargarse de nuevo. Los volúmenes Work, PVC y carpetas vinculadas al host están fuera del directorio y necesitan instantáneas y conservación coordinadas propias.
Actualizaciones
Libre WebUI tiene estado aunque la etiqueta de imagen sea mutable. Compose excluye permanentemente la aplicación de Watchtower. Actualízala solo de forma coordinada:
- Registra el ID de la imagen y resuelve el sustituto revisado a un digest inmutable.
- Ejecuta
libre-webui recovery-check, inicia la copia y exige un archivo e informe nuevos antes de continuar. - Define
LIBRE_WEBUI_IMAGEcon el digest, descárgalo y recrea sololibre-webui. No elimines ni recrees el volumen. - Exige que pasen
/health/ready, inicio de sesión, sesión e historial, recuperación de documentos y pruebas de Work. Si no, vuelve al digest registrado y conserva el estado fallido y la copia para diagnóstico.
La secuencia del host es manual. Sustituye el digest solo después de revisarlo e
inspecciona el par .lwb y .json más reciente antes de descargar:
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 sustitución opcional de Watchtower con socket queda disponible solo para los sidecars etiquetados expresamente:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower comprueba Ollama y SearXNG cada 30 minutos. Los modelos de Ollama
permanecen en su volumen y la configuración de SearXNG en su montaje. No actualiza
Libre WebUI, cloudflared, el proxy ni los entornos de Work. Un cliente sigue main;
una instancia experimental puede elegir :dev, pero aún exige la misma actualización
manual condicionada por la copia. Nunca conectes esta pila individual a servicios de
persistencia de equipo; despliega la topología completa.