Допустим, в вашей инфраструктуре уже работает Linux-кластер Docker Swarm и есть готовый сервер Ollama. Нужно добавить к ним новый экземпляр Open WebUI так, чтобы после замены контейнера учётная запись и начатый разговор остались на месте. В готовом Swarm это решается привязкой одного экземпляра сервиса к выбранному узлу с отдельным каталогом данных и постоянным секретным ключом. Сам по себе перезапуск задачи в Swarm не переносит локальную базу между машинами и не проверяет связь с языковой моделью, поэтому в уроке мы последовательно свяжем узел, хранилище, ключ и два сетевых маршрута — от браузера до интерфейса и от контейнера до Ollama.
Что именно запустим в готовом Swarm
При запуске в кластере важно различать описание сервиса и место хранения файлов. В нашей задаче мы создаём новую отдельную установку: оператор передаёт кластеру описание в виде stack, Swarm поддерживает один логический service, а внутри него запускает заменяемую задачу task — конкретный контейнер с приложением. Если этот контейнер остановится или будет принудительно обновлён, планировщик создаст вместо него новую задачу. Чтобы новая задача увидела прежние чаты и не сбросила вход пользователей, она должна открывать ту же самую папку на диске и читать тот же секретный ключ.
В этой схеме четыре роли:
- Узел управления (
manager) принимает команды развёртывания, хранит метаданные секретов и следит за тем, чтобы задача работала на предписанном месте. - Выбранный узел данных (
data-node) — конкретный Linux-сервер кластера, на котором лежит новый каталог/srv/open-webui-course/dataи запускается контейнер сервисаwebui. - Компьютер читателя — рабочее место с браузером, откуда вы подключаетесь по SSH именно к
data-node, чтобы открыть веб-интерфейс. - Существующий сервер Ollama — уже настроенный в вашей сети сервис с загруженной моделью, к которому контейнер
webuiобращается напрямую со своей стороны.
Мы ограничиваем конфигурацию одной репликой (replicas: 1) и одним рабочим процессом (UVICORN_WORKERS: "1"). По умолчанию в выпуске Open WebUI v0.12.3 (опубликован 16 октября 2026 года) база данных SQLite и локальное векторное хранилище размещаются внутри каталога данных приложения. Как отмечено в руководстве Open WebUI по многорепликовому запуску, несколько экземпляров требуют внешней базы данных и координации состояния, поэтому мы не добавляем сюда ни вторую реплику поверх SQLite, ни отдельные контейнеры PostgreSQL, Redis или Chroma.
В официальной документации Open WebUI по Docker Swarm приведён трёхсервисный рецепт, где в одном файле объединены сам веб-интерфейс, ChromaDB и Ollama. Документация прямо поясняет, что это иллюстрация совместного запуска компонентов, а не обязательное требование держать все три сервиса внутри каждого стека. Мы используем ограниченную адаптацию этого подхода для ситуации, когда сервер моделей уже существует отдельно. Кроме того, в примере из документации Open WebUI в блоке публикации портов встречается запись mode: overlay. Переносить её в рабочий файл нельзя: согласно документации Docker по сети и публикации портов в Swarm, overlay — это драйвер межузловой сети для общения контейнеров, тогда как у публикуемых наружу портов есть только два режима — ingress и host.
Разница между ingress и host напрямую влияет на безопасность и маршрут подключения. По умолчанию режим ingress включает встроенную маршрутизацию (routing mesh) и открывает опубликованный порт на всех узлах кластера, даже там, где контейнер сервиса не запущен. Нам это не нужно: данные привязаны к одному серверу, поэтому мы выбираем mode: host, который обходит общую сетку маршрутизации и открывает порт только на том узле, где работает задача. Это решение для нового экземпляра без миграции старой базы и без высокой доступности (HA): если выбранный узел данных выйдет из строя, задача перейдёт в ожидание (Pending), а сервис временно остановится, при сохранённом ограничении node.id планировщик не перенесёт контейнер на соседний сервер с пустой папкой вместо вашей базы.
Если готового кластера Swarm у вас нет и нужно просто поднять интерфейс на одной машине, создавать кластер ради этой задачи не требуется — воспользуйтесь уроком «Установка Open WebUI через Docker Compose». Если кластер уже работает, проверим его узлы, сеть и секреты перед сохранением конфигурации.
Что проверить на manager и узле данных до запуска
До любых команд развёртывания должно выполняться главное сетевое условие: в вашей инфраструктуре уже действует правило межсетевого экрана или сетевой политики, которое закрывает прямой доступ к порту 18080/tcp на выбранном узле данных из нежелательных сетей и оставляет только разрешённый маршрут оператора. Согласно документации Docker по публикации сервисных портов, режим mode: host публикует порт непосредственно на сетевых интерфейсах узла и не даёт гарантии привязки только к локальному адресу 127.0.0.1. При этом обычное правило UFW на хосте само по себе не служит доказательством защиты для портов, публикуемых через Docker. Мы не меняем правила межсетевого экрана в этом уроке: если ограничение доступа к порту 18080 на узле данных ещё не подтверждено владельцем инфраструктуры, выполнять deploy нельзя. Для публичного или командного доступа в будущем потребуется отдельно согласованный обратный прокси с HTTPS и поддержкой WebSocket, а здесь мы проверим сервис через личный SSH-туннель.
Чтобы не перепутать, где именно вводится каждая команда, сверим карту ответственности по машинам и ролям:
| Контекст или роль | Что проверяем и делаем здесь | Чего здесь делать не нужно |
|---|---|---|
Узел manager | Проверяем список стеков, находим ID выбранного узла данных, проверяем метаданные внешнего секрета, рендерим и применяем stack.yml | Не создаём здесь каталог /srv/open-webui-course/data (если manager и data-node — разные машины) и не проверяем контейнеры через docker exec |
Выбранный data-node | Проверяем, свободен ли порт 18080, создаём пустой каталог /srv/open-webui-course/data, проверяем /ready и связь контейнера с Ollama | Не запускаем здесь docker stack deploy и не храним управляющие файлы стека |
| Компьютер читателя | Открываем SSH-туннель непосредственно до data-node и заходим в интерфейс через локальный браузер по адресу http://127.0.0.1:3000 | Не направляем SSH-туннель на произвольный manager, где нет задачи сервиса |
| Оператор хранилища секретов | Заранее предоставляет в Swarm стабильный секрет openwebui_course_key_v1 из утверждённого хранилища и сохраняет отдельную резервную копию значения | Не генерирует ключ случайной командой в историю терминала и не записывает его открытым текстом в stack.yml |
Начнём с проверок на узле manager. Нам нужно убедиться, что имя стека openwebui-course свободно, выбранный узел данных находится в состоянии ready и active под управлением Linux с архитектурой amd64 или arm64, а внешний секрет openwebui_course_key_v1 уже зарегистрирован в Swarm. В блоке ниже замените DATA_NODE_NAME на реальное имя вашего узла данных, а адрес с доменом .example — на реальный URL существующего сервера Ollama, который предоставил оператор инфраструктуры.
Выполните на manager (в оболочке Bash):
Если в выводе docker stack ls уже есть стек с именем openwebui-course, остановитесь и не применяйте конфигурацию поверх существующего окружения. Команда docker node inspect должна подтвердить статус ready active linux и одну из поддерживаемых архитектур нашего образа (amd64 или arm64). Команда docker secret inspect с форматом {{.Spec.Name}} проверяет только наличие метаданных секрета в Raft-хранилище Swarm и не выводит само секретное значение на экран.
В переменной OLLAMA_BASE_URL нельзя оставлять заглушку .example: до проверки конфигурации подставьте туда действующий адрес сервера моделей, достижимый из сети будущего контейнера. Не указывайте там localhost или 127.0.0.1 — внутри контейнера WebUI эти адреса указывают на сам контейнер интерфейса, а не на хост-машину. Специальное имя host.docker.internal в Linux Swarm также не гарантировано по умолчанию, а короткое имя ollama сработает только в том случае, если оба сервиса реально подключены к одной общей overlay-сети со встроенным обнаружением сервисов (в нашей схеме общая overlay-сеть с внешним сервером моделей не предполагается). Заранее узнайте у оператора точный тег модели, которая уже скачана на этом сервере Ollama.
Секрет openwebui_course_key_v1 по правилам безопасности заранее создаёт оператор через штатный процесс управления секретами вашей организации. Как объясняется в документации Open WebUI по WEBUI_SECRET_KEY, этот ключ подписывает пользовательские сессии и участвует в шифровании чувствительных данных. Наличие объекта в docker secret inspect подтверждает, что Swarm знает о секрете, но само по себе не заменяет резервную копию его значения: оператор должен хранить копию ключа в защищённом хранилище отдельно от кластера.
Теперь перейдём на выбранный узел данных (data-node). Команды на manager не создают папок на рабочих узлах, поэтому подготовить каталог и проверить локальный порт нужно непосредственно на целевом сервере.
Выполните на выбранном data-node (в оболочке Bash на rootful Linux без режима rootless, переназначения userns-remap и особых политик SELinux):
Утилита ss показывает текущие локальные процессы, слушающие порт 18080/tcp. В норме вывод должен быть пустым. ss не видит ни правил межсетевого экрана, ни портов, зарезервированных другими неактивными сервисами в планировщике. Отсутствие конфликтов в кластере оператор проверяет дополнительно. Вторая команда проверяет, что пути /srv/open-webui-course ещё не существует, и создаёт иерархию /srv/open-webui-course/data с правами 0700 и владельцем 0:0 (root:root). Если путь уже существует, проверка test ! -e завершится ошибкой: в этом случае остановитесь, не удаляйте старые файлы наугад и не ослабляйте права доступа через chmod 777. Права 0:0 и режим 0700 точно соответствуют метаданным нашего закреплённого образа v0.12.3, процесс которого работает от пользователя 0:0. Если на ваших серверах используется userns-remap, rootless Docker или строгие профили SELinux, правила владения для bind-монтирования потребуют отдельного согласования с администратором безопасности.
Когда узел, каталог, внешний секрет, защищённый порт и адрес сервера модели подтверждены, можно собрать эти параметры в единый файл стека.
Как связать данные, ключ и версию в stack.yml
Чтобы после замены контейнера вы попадали в тот же самый экземпляр Open WebUI, файл конфигурации должен жёстко связать четыре вещи: неизменяемый образ приложения, конкретный узел с папкой данных, внешний секретный ключ и безопасный порядок перезапуска.
На узле manager в рабочем каталоге оператора сохраните файл stack.yml со следующим содержимым:
Каждая связка в этом файле защищает от конкретных ошибок:
- Формат
version: "3.8"и закреплённый digest образа. Согласно документации Docker поdocker stack deploy, развёртывание стеков в Swarm опирается на спецификацию Compose v3 (опции современного локального Compose вродеbuildилиrestartздесь не работают). В полеimageвместо плавающего тега:mainили:latestуказан точный индексный digest выпускаv0.12.3:ghcr.io/open-webui/open-webui@sha256:a537bbe1e6b428820f52ed4a029d7fff97062e24d5cf32c365fba36fd0542def. Digest закрепляет один индекс образа; при пересоздании задачи на той же архитектуре узел использует ту же платформенную сборку (проверка опирается на метаданные реестра GHCR и исходный код релизаv0.12.3). - Жёсткая привязка
node.idи локальный каталог данных. В разделеvolumesиспользуетсяtype: bind, который монтирует абсолютный путь/srv/open-webui-course/dataвыбранного узла в контейнерный каталог/app/backend/data. Как предупреждает документация Docker по томам в Swarm, локальные тома иbind-монтирования принадлежат конкретной машине, а не всему кластеру. Поэтому вdeploy.placement.constraintsзадано жёсткое условиеnode.id == ${DATA_NODE_ID:?...}. Конструкция:?не позволит сгенерировать файл, если переменнаяDATA_NODE_IDпуста, а само ограничение запретит планировщику переносить задачу на другие серверы. - Связка внешнего секрета и
WEBUI_SECRET_KEY_FILE. По умолчанию стартовый скрипт контейнераbackend/start.shсоздаёт файл.webui_secret_keyв рабочей папке/app/backend, которая находится вне примонтированного тома/app/backend/data. Если бы мы сохранили только папкуdata, при первой же замене контейнера ключ внутри слоя образа сменился бы и существующие сессии перестали бы работать. Согласно документации Docker Secrets, Swarm монтирует внешний секретopenwebui_course_key_v1в память контейнера по пути/run/secrets/openwebui_secret_key. Мы указываем этот путь в переменнойWEBUI_SECRET_KEY_FILEи сохраняем штатную команду запуска образа (bash start.sh). В кодеbackend/start.shрелизаv0.12.3переменнаяWEBUI_SECRET_KEY_FILEчитается только тогда, когда не заданы прямые переменныеWEBUI_SECRET_KEYи устаревшаяWEBUI_JWT_SECRET_KEY. Поэтому не добавляйте эти две переменные вenvironmentи не переносите суффикс_FILEна произвольные ключи API — для них такая логика в стартовом скрипте не предусмотрена. - Публикация порта
18080 -> 8080в режимеhostи включённая аутентификация. ПеременнаяWEBUI_AUTH: "True"включает обязательный вход по учётной записи. Блокportsсвязывает внутренний порт приложения8080с портом18080на узле данных в режимеmode: host. Внутренние порты других сервисов (такие как11434для Ollama или8000для Chroma) мы здесь не публикуем. - Одна реплика и порядок обновления
stop-first. Параметрыreplicas: 1иUVICORN_WORKERS: "1"ограничивают запуск одним экземпляром приложения с одним рабочим процессом. А настройкиorder: stop-first,parallelism: 1иfailure_action: pauseв блокахupdate_configиrollback_config(см. справочник спецификации Deploy) требуют от Swarm сначала полностью остановить старую задачу, освободить порт18080и закрыть базу SQLite, и лишь затем запускать новый контейнер. Если Swarm признает обновление неуспешным по его условиям контроля,failure_action: pauseприостановит дальнейшее обновление. Этот параметр не отменяет политику перезапуска самой задачи. Во время замены контейнера интерфейс на короткое время станет недоступен.
Теперь проверим, как Docker подставит наши переменные в этот шаблон, и передадим проверенный файл планировщику.
Как применить stack и проверить задачу Swarm
Перед отправкой описания в кластер разделим два шага: сначала локально проверить структуру YAML и подстановку переменных через docker stack config, сохранить результат в отдельный файл stack.rendered.yml и только после этого выполнить docker stack deploy.
Убедитесь, что в вашей текущей сессии терминала на manager по-прежнему заданы непустые переменные DATA_NODE_ID и OLLAMA_BASE_URL из второго раздела, а в текущей папке лежит stack.yml.
Выполните на manager (в оболочке Bash из рабочего каталога со stack.yml):
Команда docker stack config выполняет локальную интерполяцию переменных окружения и проверяет синтаксис файла без обращения к демону Swarm. Если DATA_NODE_ID или OLLAMA_BASE_URL не заданы, команда завершится с ошибкой. Перенаправление > при этом уже может создать или обнулить stack.rendered.yml: остановитесь и не выполняйте deploy, пока docker stack config не завершится успешно. Внутри stack.rendered.yml остаётся только ссылка на имя внешнего секрета openwebui_course_key_v1, а само секретное значение туда не попадает.
У этой предварительной проверки есть границы. При подготовке урока мы провели офлайн-валидацию нашего stack.yml с помощью docker stack config (Docker CLI 29.6.1), проверили синтаксис всех Bash-команд и 12 структурных инвариантов отрендеренного манифеста, но не запускали реальный кластер Swarm. Показательная деталь из этой офлайн-проверки: парсер docker stack config проверяет схему YAML, но не проверяет все семантические правила движка Swarm — в контрольном отрицательном тесте он без ошибки пропустил даже заведомо невалидный режим порта mode: overlay. Кроме того, docker stack config не знает, существует ли в кластере указанный узел, создана ли папка на диске, свободен ли порт 18080 и доступен ли секрет.
Именно поэтому сразу после docker stack deploy мы проверяем фактическое состояние сервиса в планировщике тремя последними командами:
- В выводе
docker stack services openwebui-courseу сервисаopenwebui-course_webuiв колонкеREPLICASдолжно появиться значение1/1. - В выводе
docker service ps --no-trunc openwebui-course_webuiтекущее состояние (CURRENT STATE) должно перейти вRunning, колонкаNODEдолжна указывать точно на ваш выбранный узел данных, а полеERRORдолжно оставаться пустым. Флаг--no-truncздесь обязателен: без него Docker обрезает длинные сообщения об ошибках и полные идентификаторы. - Команда
docker service inspectдолжна вернуть точную строку нашего образа с неизменённым индексным хешем@sha256:a537bbe1e6b428820f52ed4a029d7fff97062e24d5cf32c365fba36fd0542def.
Если задача застряла в состоянии Pending, Rejected или Failed, посмотрите на текст в колонке ERROR вывода docker service ps --no-trunc и сопоставьте причину с местом проверки:
- Ошибки
no suitable node(не выполнено ограничениеconstraintилиavailability) — проверьте наmanager, правильный лиDATA_NODE_IDпопал вstack.rendered.yml, а также находится ли целевой узел в состоянииreadyиactive. Никогда не удаляйте ограничениеnode.idиз файла ради того, чтобы сервис позеленел на другом узле: там не окажется вашей папки данных. - Ошибки монтирования (
mountилиbind source path does not exist) — проверьте наdata-node, создан ли точный путь/srv/open-webui-course/dataи соответствуют ли его права условиям вашего окружения. - Конфликт порта (
portуже занят или зарезервирован) — проверьте слушателей порта18080наdata-nodeчерезssи убедитесь наmanager, что этот порт не закреплён за другим сервисом. - Ошибка доступа к секрету (
secret not found) — проверьте наmanagerточное имяopenwebui_course_key_v1черезdocker secret inspect. - Ошибка загрузки образа (
image, доступ к реестру или несовпадение платформы) — проверьте доступ узлаdata-nodeкghcr.io, сохранность digest-строки и архитектуру процессора (amd64илиarm64).
Если задача перешла в Running, но ведёт себя нестабильно, на узле manager можно просмотреть последние 50 строк журнала сервиса:
Читайте этот вывод локально в своём терминале; если потребуется переслать фрагмент журнала коллегам, предварительно убедитесь, что в строки не попали внутренние адреса или другие чувствительные сведения вашей среды.
Даже когда в docker stack services горит 1/1, а задача находится в статусе Running, это означает лишь то, что планировщик Swarm создал контейнер на нужном сервере. Чтобы убедиться, что приложение внутри контейнера завершило инициализацию базы данных и действительно видит сервер Ollama, перейдём к проверкам на узле данных.
Как проверить готовность приложения и связь с Ollama
Почему нельзя сразу открывать браузер, ориентируясь только на статус Running и встроенную проверку здоровья образа? Если заглянуть в Dockerfile и исходный код backend/open_webui/main.py выпуска v0.12.3, окажется, что встроенный HEALTHCHECK контейнера опрашивает маршрут /health, а обработчик этого маршрута просто возвращает константу {'status': True}, как только запустился веб-сервер. В том же файле main.py есть более строгий маршрут /ready: он возвращает успешный ответ только после завершения стартовых процедур приложения и проверки подключения к базе данных (а также к Redis, если бы он был настроен).
Перейдём в терминал выбранного узла данных (data-node), проверим готовность приложения через /ready и найдём идентификатор запущенного контейнера.
Выполните на выбранном data-node (в оболочке Bash):
Первая команда обращается к локальному порту 18080 на самом узле данных и проверяет, что приложение ответило со статусом HTTP 200 без ошибок. Вторая команда выводит короткий идентификатор (CONTAINER_ID) единственного работающего контейнера нашей задачи. Если docker ps не вернул ни одного идентификатора или вывел сразу несколько строк, не угадывайте нужный контейнер — сначала вернитесь на manager и разберитесь с состоянием задач через docker service ps.
Когда у вас есть единственный идентификатор текущего контейнера, проверим вторую сетевую связь: способен ли сам контейнер webui достучаться до сервера Ollama по адресу из переменной OLLAMA_BASE_URL. Проверять это командой curl с узла manager или напрямую с хоста data-node недостаточно, поскольку у контейнера своё сетевое пространство имён и свои маршруты. Кроме того, мы не устанавливаем внутрь образа сторонние утилиты, а используем штатный Python, который уже входит в контейнер Open WebUI.
На выбранном data-node подставьте полученный идентификатор вместо CONTAINER_ID и выполните:
Однострочник берёт значение OLLAMA_BASE_URL непосредственно из окружения работающего контейнера и запрашивает у сервера моделей служебный маршрут /api/tags, возвращающий список установленных моделей. Код ответа 200 в выводе подтверждает, что сетевой маршрут от задачи Swarm до API Ollama работает.
У этой проверки два ограничения. Во-первых, HTTP-код 200 от /api/tags подтверждает доступность API, но сам по себе не гарантирует, что список моделей не пуст и что сервер способен выполнить генерацию ответа; наличие конкретной загруженной модели мы проверим на следующем шаге в интерфейсе. Во-вторых, как отмечено в справочнике конфигурации Open WebUI по OLLAMA_BASE_URL, настройки подключений относятся к параметрам PersistentConfig, которые после изменения через панель администратора могут сохраняться в базе данных приложения. Поскольку мы развернули чистый экземпляр с новой базой в /srv/open-webui-course/data, при первом старте приложение берёт адрес из переменной окружения OLLAMA_BASE_URL. Но если позже вы поменяете этот URL вручную в веб-интерфейсе, повторное развёртывание стека с новым значением переменной в stack.yml не обязана автоматически перезаписать сохранённую в базе настройку — при диагностике всегда сверяйте переменную окружения с тем, что фактически указано в разделе администратора.
Убедившись, что на стороне сервера готовы и база данных приложения, и сетевой путь до Ollama, проверим работу чата глазами пользователя.
Как открыть интерфейс и получить первый ответ
Теперь пройдём путь от вашего рабочего компьютера до веб-интерфейса. Поскольку порт 18080 опубликован в режиме mode: host только на выбранном узле данных, пробрасывать SSH-туннель нужно именно до data-node, а не до произвольного узла manager (если в вашем кластере это разные серверы). Убедитесь, что локальный порт 3000 на вашем компьютере свободен.
На компьютере читателя замените DATA_NODE_SSH на разрешённое имя или адрес SSH-подключения к вашему узлу данных и выполните в терминале:
Флаг -N указывает SSH не запускать удалённую командную оболочку, а только удерживать перенаправление портов: обращения к 127.0.0.1:3000 на вашем компьютере теперь передаются по шифрованному соединению на адрес 127.0.0.1:18080 выбранного узла данных. Не закрывайте это окно терминала на время работы в браузере. Сам по себе SSH-туннель — это удобный личный канал связи, а не сетевой экран; он не закрывает порт 18080 на внешних интерфейсах сервера, поэтому мы и проверяли правила фильтрации трафика до развёртывания.
Оставив туннель открытым, перейдите в браузере на своём компьютере по адресу http://127.0.0.1:3000 и последовательно проверьте четыре слоя пользовательского сценария:
- Загрузка страницы и создание первого администратора. На новой установке Open WebUI предложит создать первую учётную запись, которая автоматически получит права администратора. После входа сразу отключите свободную самостоятельную регистрацию новых пользователей до того, как решите кого-либо приглашать; подробно эти настройки разобраны в уроке «Первый вход и базовые настройки доступа».
- Проверка подключения к Ollama в панели администратора. Перейдите в настройки администратора (Settings → Admin Settings → Connections) и убедитесь, что в поле адреса Ollama отображается ваш рабочий URL, а проверка соединения проходит успешно (подробности настройки интерфейса описаны в материале «Подключаем локальную модель через Ollama»). В этом сценарии с внутренним сервером Ollama мы не добавляем никаких внешних API-ключей.
- Выбор модели и получение первого ответа. Вернитесь на главный экран чата, выберите в выпадающем списке тот модельный тег, который заранее подготовил оператор вашего сервера Ollama, и отправьте короткий проверочный вопрос. Дождитесь осмысленного ответа в окне диалога. Для этой проверки скачивать новые модели через интерфейс не нужно.
- Сохранение тестового диалога. Убедитесь, что созданный диалог появился в боковой панели истории чатов под вашей учётной записью.
Загрузившаяся страница входа, успешная авторизация, видимый в списке тег модели и реально сгенерированный ответ — это четыре разные проверки, которые читатель наблюдает в своей разрешённой среде. Теперь, когда в базе данных /srv/open-webui-course/data есть ваша учётная запись и сохранённый диалог, мы можем проверить главный вопрос урока: переживёт ли этот чат принудительную замену контейнера в Swarm.
Что сохранится после замены задачи и где границы восстановления
В работающем кластере задача сервиса может быть пересоздана планировщиком или перезапущена оператором. Проверим на нашем новом тестовом экземпляре, как поведёт себя Open WebUI, если принудительно заменить его задачу тем же самым образом без изменения файла конфигурации, узла, каталога и секрета. Поскольку в stack.yml задано правило order: stop-first и одна реплика, учтите, что на время перезапуска контейнера веб-интерфейс кратковременно перестанет отвечать.
На узле manager выполните команду принудительного обновления сервиса:
Флаг --force заставляет Swarm остановить текущую задачу сервиса openwebui-course_webui и запустить взамен новый контейнер с теми же параметрами, а --detach=false оставляет команду ждать сходимости сервиса; при проблемах нужно смотреть состояние задач. Мы не меняем здесь ни digest образа, ни секрет openwebui_course_key_v1, ни путь /srv/open-webui-course/data, ни ограничение node.id.
Когда команда завершится, повторите уже знакомые проверки в том же порядке:
- На
managerпроверьте черезdocker service ps --no-trunc openwebui-course_webuiиdocker service inspect, что предыдущая задача перешла в состояниеShutdown, а на том же самом узле данных (data-node) запустилась новая задачаRunningс тем же индексным digest образаv0.12.3. - На
data-nodeдождитесь успешного ответа отcurl -fsS http://127.0.0.1:18080/ready, получите черезdocker psновыйCONTAINER_IDи при необходимости убедитесь тем жеdocker exec ... urllib.request, что новый контейнер видит/api/tagsсервера Ollama. - На компьютере читателя (при работающем SSH-туннеле) обновите страницу
http://127.0.0.1:3000в браузере, проверьте, что вход в учётную запись работает, ранее сохранённый тестовый диалог открывается в истории, а модель отвечает на следующее сообщение в том же чате.
Если после замены контейнера тестовый разговор сохранился, это означает, что новая задача прочитала прежние данные. Ведь сама задача (task) в Swarm — это временный процесс, а состояние вашего экземпляра определяется тремя постоянными привязками за пределами контейнера: жёстким правилом размещения node.id, папкой /srv/open-webui-course/data на диске этого узла и неизменным секретом openwebui_course_key_v1, подключённым через WEBUI_SECRET_KEY_FILE.
Замену задачи и восстановление после сбоев или обновлений нужно различать:
- Замена задачи того же образа (
--force) проверяет только то, что новый контейнер корректно подхватывает существующую папку данных и ключ на прежнем узле. Это не проверка резервного копирования и не доказательство отказоустойчивости кластера: если сам серверdata-nodeвыключится или станет недоступен, жёсткое ограничениеnode.idоставит задачу в состоянииPending, и сервис будет простаивать до возвращения узла в строй. - Обновление версии образа (upgrade) выполняется иначе: в исходном файле
stack.ymlзаменяют индексный digest на новый проверенный выпуск, после чего снова прогоняютdocker stack configиdocker stack deploy. Перед таким обновлением необходимо остановить приложение (чтобы прекратить запись в SQLite), сделать полную консистентную копию всего каталога/srv/open-webui-course/data, а также убедиться, что у вас отдельно сохранены значение стабильного ключаopenwebui_course_key_v1, файлstack.ymlи прежний digest образа. Копировать файлы активно работающей базы SQLite «на лету» в расчёте на гарантированный бэкап нельзя. - Откат сервиса в Swarm (
rollback) не откатывает базу данных. Согласно документации Docker по откату сервисов и руководству Open WebUI по обновлению, откат в Swarm возвращает лишь прежнюю конфигурацию сервиса и старый образ контейнера, но не отменяет изменения на диске. Миграции базы данных в Open WebUI односторонние (Database migrations are one-way): если новая версия образа уже изменила схему SQLite, старый контейнер после автоматического отката может не запуститься поверх обновлённой базы. - Полноценное восстановление (recovery) — это согласованная процедура оператора, при которой на остановленном сервисе возвращают совместимую версию образа, восстанавливают из проверенной копии каталог данных и подключают тот же самый секретный ключ.
Сохраните свой рабочий stack.yml, отрендеренный stack.rendered.yml и записи о выбранном узле и секрете в документации вашей инфраструктуры. А прежде чем планировать переход на следующую версию образа или настраивать регламент резервного копирования, изучите соседний урок «Обновления, резервная копия и безопасная эксплуатация».

