Перейти до основного вмісту

Приватне віддалене розгортання

У цій конфігурації 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 вимкнено, доки не підключено один із його файлів перевизначення. Контейнери Work отримують власну кореневу файлову систему лише для читання, скинуті capabilities, обмеження ресурсів, том робочої області й мережеву політику заборони за замовчуванням.

Базовий стек не підключає сокет Docker. Якщо Work увімкнено через docker-compose.work-proxy.yml, ця властивість зберігається: проксі сокета у внутрішній мережі володіє сокетом і пересилає лише розділи API, потрібні Work (containers, images, volumes, networks, exec, info). Розділи swarm, secrets, build і system блокуються на проксі, а застосунку не потрібні ні підключення сокета, ні членство в його групі. Проксі звужує поверхню Docker API, але не масштаб можливої шкоди від дозволених операцій: процес, здатний створювати контейнери, усе ще може підключати шляхи хоста. Тому це справжній рівень посилення захисту, але не ізоляція кількох орендарів.

Варіанти з необробленим сокетом залишаються найширшою межею довіри: docker-compose.work.yml і перевизначення Watchtower надають контейнеру процес, здатний надсилати довільні виклики Docker API й керувати хостом. Підключення сокета лише для читання не робить доступ до Docker API доступом лише для читання. Вбудований інструмент резервного копіювання відмовляється успадковувати необроблений сокет; перш ніж покладатися на заплановані інтегровані копії, переведіть Work на фільтрований проксі.

Початкове налаштування

  1. Створіть оператора без root із правом sudo й перевірте вхід SSH за ключем, перш ніж вимикати вхід root через SSH.
  2. Скопіюйте deploy/private/.env.example до /opt/libre-webui/.env, задайте режим 0600, створіть унікальні секрети й доберіть BLOB_QUOTA_BYTES_PER_USER для хоста. Змінна BLOB_QUOTA_RESERVATION_TTL_MS обмежує строк покинутих резервувань завантаження; значення за замовчуванням — одна година.
  3. Якщо буде ввімкнено Work, задайте DOCKER_GID числовим ідентифікатором групи, що володіє /var/run/docker.sock.
  4. Збережіть токен Cloudflare Tunnel у /opt/libre-webui/secrets/tunnel-token з режимом 0640 або суворішим.
  5. Створіть у Cloudflare Access самостійно розміщений застосунок для повного імені хоста, задайте 24-годинний сеанс і дозвольте лише потрібні ідентичності. Увімкніть Protect with Access для маршруту Tunnel. Якщо системі моніторингу потрібна загальнодоступна перевірка стану, створіть окремий застосунок або політику лише для /health/live. Ніколи не додавайте загальну політику Bypass до основного застосунку: збіг Bypass скасовує його політику Allow.
  6. Залиште ENABLE_SIGNUP=false. Після того як список дозволів Access захистить ім’я хоста, створіть першого локального адміністратора; порожня база автоматично допускає цей єдиний початковий обліковий запис. Пізніше вмикайте реєстрацію лише на навмисно обмежений час.
  7. Налаштуйте обмеження імені хоста 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. Оновлюйте його лише узгодженою дією оператора:

  1. Запишіть ID робочого образу й дозволяйте лише перевірену заміну на незмінний digest.
  2. Виконайте libre-webui recovery-check, запустіть службу резервного копіювання й вимагайте новий архів і звіт перевірки, перш ніж продовжувати.
  3. Задайте LIBRE_WEBUI_IMAGE перевіреним digest, завантажте образ і повторно створіть через Docker Compose лише libre-webui. Не видаляйте й не створюйте заново том даних.
  4. Вимагайте успішних /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.