Приватное удалённое развёртывание
В этой конфигурации Libre WebUI, Ollama и Cloudflare Tunnel работают на одном Docker-хосте без публикации портов приложения или Ollama. Cloudflare Access образует внешнюю границу идентификации, а аутентификация Libre WebUI остаётся внутренней. Work и Watchtower включаются отдельно и предоставляют права, эквивалентные root.
Шаблон рассчитан на топологию solo с одной репликой: SQLite, локальные зашифрованные блобы, встроенные векторы, локальная координация и встроенный обработчик постоянных заданий совместно используют том данных приложения. Не превращайте эту конфигурацию в team-развёртывание сменой селекторов бэкенда в .env. Для team необходимо использовать docker-compose.team.yml из репозитория (и docker-compose.team.work.yml, если включён Work). Эти файлы совместно предоставляют PostgreSQL/PGVector, версионированное хранилище S3, Redis, внешний обработчик заданий и шлюз как единую согласованную топологию.
В качестве отправной точки используйте deploy/private/docker-compose.yml. По умолчанию он использует образ main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
Тег dev подходит только для явно выбранного экземпляра разработки, а не для обычного клиентского развёртывания.
Модель безопасности
- Cloudflare Access защищает всё имя хоста, включая
/api/*и обновления WebSocket. Не добавляйте публичные пути обхода. - API приложения требуют действующую учётную запись Libre WebUI. Операции жизненного цикла моделей и Work требуют, чтобы текущая роль в базе данных была ролью администратора.
- Приложение, Ollama, SearXNG и cloudflared используют только приватную сеть Compose. Хост не публикует порты приложения.
- Встроенная служба SearXNG обеспечивает необязательный веб-поиск. Она доступна только внутри сети и бездействует, пока администратор не включит поиск в Settings > Search; до запуска стека задайте
SEARXNG_SECRETв.env. - Приложение работает без root, с корневой файловой системой только для чтения, без дополнительных возможностей Linux (capabilities), с no-new-privileges и лимитами CPU, памяти и PID.
- Work отключён, пока не подключён один из его файлов переопределения. Его контейнеры получают собственную корневую файловую систему только для чтения, сброшенные capabilities, лимиты ресурсов, том рабочей области и сетевую политику запрета по умолчанию.
Базовый стек не подключает сокет Docker. При включении Work через docker-compose.work-proxy.yml это свойство сохраняется: прокси сокета во внутренней сети владеет сокетом и пересылает только используемые Work разделы API (containers, images, volumes, networks, exec, info). Разделы swarm, secrets, build и system блокируются на прокси, а приложению не нужны ни подключение сокета, ни членство в его группе. Прокси сужает поверхность Docker API, но не масштаб возможного ущерба от разрешённых операций: процесс, способный создавать контейнеры, всё ещё может подключать пути хоста. Поэтому это реальный уровень усиления защиты, но не изоляция нескольких арендаторов.
Варианты с необработанным сокетом остаются самой широкой границей доверия: docker-compose.work.yml и переопределение Watchtower предоставляют контейнеру процесс, способный отправлять произвольные вызовы Docker API и управлять хостом. Подключение сокета только для чтения не делает доступ к Docker API доступом только для чтения. Встроенный инструмент резервного копирования отказывается наследовать необработанный сокет; прежде чем полагаться на запланированные интегрированные копии, переведите Work на фильтрованный прокси.
Первоначальная настройка
- Создайте оператора без root с правом sudo и проверьте вход SSH по ключу, прежде чем отключать вход root по SSH.
- Скопируйте
deploy/private/.env.exampleв/opt/libre-webui/.env, задайте режим0600, создайте уникальные секреты и подберитеBLOB_QUOTA_BYTES_PER_USERдля хоста. ПеременнаяBLOB_QUOTA_RESERVATION_TTL_MSограничивает срок оставленных резервирований загрузки; значение по умолчанию — один час. - Если будет включён Work, задайте
DOCKER_GIDчисловым идентификатором группы, владеющей/var/run/docker.sock. - Сохраните токен Cloudflare Tunnel в
/opt/libre-webui/secrets/tunnel-tokenс режимом0640или более строгим. - Создайте в Cloudflare Access самостоятельно размещаемое приложение для полного имени хоста, задайте 24-часовой сеанс и разрешите только нужные идентичности. Включите Protect with Access для маршрута Tunnel. Если системе мониторинга нужна публичная проверка состояния, создайте отдельное приложение или политику только для
/health/live. Никогда не добавляйте общую политику Bypass к основному приложению: совпавшая Bypass отменяет его политику Allow. - Оставьте
ENABLE_SIGNUP=false. После того как список разрешений Access защитит имя хоста, создайте первого локального администратора; пустая база автоматически допускает эту единственную первоначальную учётную запись. Включайте регистрацию позднее только на намеренно ограниченное время. - Настройте ограничения имени хоста Turnstile и задайте
TURNSTILE_EXPECTED_HOSTNAMEточным публичным именем.
Запустите и проверьте стек:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Чтобы включить Work, намеренно добавьте переопределение прокси сокета:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
Вариант с необработанным сокетом (docker-compose.work.yml) остаётся доступным для развёртываний, которым он необходим, с описанными выше последствиями для доверия.
После включения Access для проверок из командной строки нужен служебный токен Cloudflare Access, если точный путь не имеет узкого обхода. Храните учётные данные вне истории командной оболочки и отправляйте оба заголовка:
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
Неаутентифицированный запрос к защищённому API приложения должен вернуть 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
Усиление защиты хоста
В каталоге есть дополнительная конфигурация sshd и jail fail2ban. Перед её применением проверьте в другом терминале отдельный сеанс оператора без root с sudo. Перед перезагрузкой SSH проверьте конфигурацию командой sshd -t.
Настройте UFW или эквивалентный брандмауэр на запрет входящего трафика по умолчанию и разрешите только SSH с ограничением частоты. В этом шаблоне Docker не публикует порты служб:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Оставьте автоматические обновления безопасности включёнными. Отключите переадресацию X11, агента и TCP, если развёртывание не имеет задокументированной потребности в них.
Резервные копии и восстановление
Перед созданием копии запустите инвентаризацию восстановления только для чтения внутри работающего контейнера. Так проверяются точная развёрнутая версия приложения, окружение и подключённый том данных. Команда из рабочей копии на хосте может проверить другую базу или исходный код, отличающийся от образа.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
Код выхода 0 означает отсутствие блокировок готовности к восстановлению, 1 — наличие блокировок в JSON-отчёте, 2 — невозможность выполнить команду. Отчёт содержит только отпечаток ключа шифрования и признаки наличия секретов; ключи и другие значения секретов никогда не выводятся. Храните инвентаризацию вместе с соответствующей копией, чтобы до восстановления сравнить версию приложения, отпечаток схемы, ожидаемые ресурсы Work и исключения.
Создайте отдельные ключи шифрования и подписи резервных копий с помощью точно развёрнутого образа. Держите каталог вне тома приложения и скопируйте ключ шифрования и приватный ключ подписи в отдельное защищённое место восстановления:
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
Генерация ключей отказывается перезаписывать существующие файлы. Никогда не создавайте новые ключи поверх существующего набора копий: потеря ключа шифрования архива или идентичности подписи делает соответствующее доказательство восстановления непригодным.
Установите предоставленные скрипты резервного копирования и восстановления и модули systemd, затем включите таймер:
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
Модуль необязательно читает предназначенные только для обслуживания переопределения из /etc/libre-webui/backup.env; файл .env приложения не загружается. Создавайте файл от 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 и LIBRE_WEBUI_BACKUP_KEY_DIR. Владельцем должен оставаться root, режим — 0600. Пользовательский каталог ключей должен быть доступен root внутри песочницы systemd.
Изменение LIBRE_WEBUI_BACKUP_DIR также меняет разрешённую для записи границу systemd. Каталог должен существовать до запуска службы, а модулю требуется соответствующее дополнительное правило. Например, после задания LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui в backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
Добавьте в редакторе точный путь, затем перезагрузите модуль:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Без соответствующей записи ReadWritePaths= параметр ProtectSystem=strict правильно запрещает таймеру записывать в пользовательский каталог.
Служба резервного копирования допускает до шести часов для крупных архивов. Инструмент получает блокировку хоста, останавливает приложение, только если оно работало, и создаёт архив остановленного тома с точным развёрнутым образом. Архив содержит подписанный манифест и зашифрованные оператором данные; в него входят каталог данных, конфигурация среды выполнения и секреты, необходимые для открытия состояния. Затем инструмент независимо проверяет весь архив и атомарно публикует отчёт метаданных. Контейнеры обслуживания с файловой системой только для чтения получают приватный записываемый tmpfs /tmp для проверки SQLite и архива; временный открытый текст не сохраняется в слое контейнера. Скопируйте оба файла и отдельно защищённые ключи восстановления за пределы хоста.
Когда Work использует docker-compose.work-proxy.yml, восстановление также должно доказать существование каждого тома Work, указанного в базе. Инструмент читает DOCKER_HOST развёрнутого приложения, находит службу socket-proxy в том же работающем проекте Compose и определяет одну общую внутреннюю сеть по фактическим подключениям Docker. Compose добавляет к сети имя проекта, поэтому не настраивайте и не фиксируйте предполагаемое имя сети. Только контейнер создания архива присоединяется к внутренней сети и может достигнуть фильтрованного прокси; необработанный сокет ему не предоставляется. Независимая проверка архива выполняется с --network none. Отсутствующий прокси, неожиданный эндпоинт, внешняя или неоднозначная общая сеть либо подключение необработанного сокета приводят к ошибке до остановки приложения и публикации архива.
Проверьте восстановление в новый том, не заменяя рабочий:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
Инструмент восстановления отказывается использовать существующий том или каталог конфигурации, проверяет архив и внутреннюю инвентаризацию во временном хранилище, затем копирует данные в новый том и записывает восстановленные runtime.json и secrets.json с приватными правами. Он никогда не перенастраивает и не запускает рабочий стек. Проверьте восстановленную конфигурацию, намеренно обновите значения, зависящие от развёртывания, и испытайте том в изолированном стеке.
Модели Ollama можно загрузить повторно. Тома Docker Work, PVC Kubernetes Work и каталоги Work, связанные с хостом, находятся вне каталога данных приложения и требуют собственных согласованных снимков и политики хранения.
Обновления
Libre WebUI хранит состояние, даже если тег образа изменяем. Базовый файл Compose постоянно помечает приложение как исключённое из Watchtower. Обновляйте его только согласованным действием оператора:
- Запишите ID работающего образа и разрешите проверенную замену в неизменяемый digest.
- Выполните
libre-webui recovery-check, запустите службу резервного копирования и потребуйте новый архив и отчёт проверки, прежде чем продолжать. - Задайте
LIBRE_WEBUI_IMAGEпроверенным digest, загрузите образ и пересоздайте через Docker Compose толькоlibre-webui. Не удаляйте и не пересоздавайте том данных. - Потребуйте успешные
/health/ready, вход, проверку сеанса/истории, извлечение документов и базовые тесты Work. При ошибке вернитесь к записанному digest образа; сохраните ошибочное состояние и проверенную копию для диагностики.
Последовательность на хосте намеренно выполняется вручную. Заменяйте digest только после проверки и просмотрите новейшую пару .lwb и .json перед загрузкой:
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 с сокетом остаётся доступным только для явно отмеченных вспомогательных контейнеров из базового файла:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower проверяет Ollama и SearXNG каждые 30 минут. Данные моделей Ollama остаются в именованном томе, а конфигурация SearXNG — в подключённом каталоге хоста. Watchtower не обновляет Libre WebUI, cloudflared, прокси сокета Work или песочницы Work. Клиентское развёртывание следует за main; экспериментальный экземпляр может выбрать :dev, но приложение всё равно требует ручного обновления через обязательную резервную копию. Никогда не подключайте этот приватный стек solo к службам постоянства team; вместо этого развёртывайте полную топологию team.