Главная / Статьи / Self-hosting Ollama и Open WebUI в Docker для команды

Self-hosting Ollama и Open WebUI в Docker для команды

Замените разрозненные установки Ollama стеком Docker Compose: внутренний Ollama, Open WebUI с аутентификацией, TLS через Caddy, проброс GPU, бэкапы и решения типичных проблем.

1974 слов

Локальный ИИ обычно начинается с одного человека, одной рабочей станции и одного удачного вечера. Кто-то ставит Ollama, скачивает модель, и ответы в чате отличные. Потом коллега хочет то же самое. Быстрый ответ — перекинуть файлы модели на флешке, поставить Ollama на вторую машину и записать, какие команды запускать. Через месяц у вас четыре машины, три версии Ollama, несовпадающие файлы моделей и общая таблица, где IP-адреса сопоставлены портам.

Такая схема не выдерживает нагрузки. Она съедает диск, позволяет конфигурации расползаться и незаметно делает того, кто собрал первую машину, бесплатным сисадмином хрупкого сервиса. Ниже она заменяется контейнерным стеком, где Ollama работает во внутренней сети, Open WebUI отвечает за вход, а наружу смотрит обратный прокси.

Почему ручная раздача не работает

Проблемы обычно проявляются только через несколько недель использования.

Дублирование хранилища. Модель 7B с квантизацией Q4 занимает около 4 ГБ. Модели 70B нужно ближе к 40 ГБ. Копирование этих файлов на каждую машину команды забивает NVMe-диски и перегружает локальный коммутатор.

Расхождение версий. Если у одного старый рантайм, а у другого более новая сборка, поведение расходится. Модели могут падать без ошибки, или два человека получают разный вывод на один и тот же промпт. Отладка превращается в охоту за версиями ПО вместо поиска первопричин.

Открытость в сеть. Это самое опасное. Типичный короткий путь — выставить OLLAMA_HOST=0.0.0.0 и открыть порт 11434 в файрволе. Это выводит сырой API исполнения в сеть без аутентификации, шифрования и ограничения частоты запросов. Любой, кто дотянется до адреса, может запускать недоверенные нагрузки, жечь вычисления, читать активный контекст или подменять сохранённые веса.

Контейнерная установка убирает все три проблемы, делая окружение декларативным. Файл Docker Compose становится точной спецификацией. Данные моделей живут в отдельных постоянных томах, сетевой доступ сегментирован, а стек поднимается одинаково на любом Linux-хосте.

Схема

Сырое исполнение моделей отделено от всего, с чем работают пользователи. Ollama работает только внутри внутренней bridge-сети. Open WebUI стоит перед ним и занимается аутентификацией. Представьте это как слои снизу вверх:

  • Linux-хост с драйвером NVIDIA или ROCm
  • Docker bridge-сеть с именем ai_network (172.28.0.0/16)
  • Контейнер Ollama (движок инференса, внутренний порт 11434, GPU проброшен напрямую)
  • Контейнер Open WebUI (веб-приложение плюс база данных, внутренний порт 8080, аутентификация обязательна)
  • Именованный том ollama_models для Ollama и том open_webui_data для Open WebUI
  • Open WebUI опубликован только на 127.0.0.1:3000, перед ним обратный прокси (Caddy или Nginx с TLS), а за ним аутентифицированные пользователи

За что отвечает каждая часть

Прежде чем что-то связывать, у каждого слоя должны быть чёткие границы.

Ollama — только бэкенд для инференса. Он загружает веса в память, распределяет тензорные операции по GPU или CPU и отдаёт HTTP REST API. В нём намеренно нет управления пользователями, сессий и мультиарендной авторизации.

Его состояние целиком хранится в хранилище моделей, заданном OLLAMA_MODELS, по умолчанию это /root/.ollama внутри контейнера. При скачивании модели слои загружаются прямо в этот том. Поскольку больше Ollama ничего не хранит, образ контейнера можно удалить или заменить, и все скачанные модели останутся на диске.

Open WebUI — веб-приложение и слой оркестрации, которым люди реально пользуются. Он хранит учётные данные, историю чатов, применяет ролевые права и запускает конвейеры Retrieval Augmented Generation. Сам он никакой математики моделей не делает: он превращает действия в браузере в API-вызовы к внутреннему сервису Ollama. Его собственное состояние лежит во встроенной базе SQLite и локальных файлах в томе open_webui_data. Потеря этого тома ударит по аккаунтам и истории переписки, но не тронет бинарники моделей.

Docker даёт изоляцию, сетевую политику и воспроизводимый запуск. Поскольку лимиты и монтирования томов описаны в одном манифесте, системные зависимости не зависят от того, что установлено в ОС хоста.

Почему не десктопное приложение

Headless-сервисы часто сравнивают с десктопными GUI-инструментами вроде LM Studio. LM Studio — отличная песочница для одного исследователя, пробующего промпты. Но в нём нет встроенной многопользовательской очереди токенов, управления фоновыми сервисами и модульной микросервисной интеграции. Ollama в Docker даёт поведение демона, единый API и чистое разделение, которых требует общее развёртывание.

Выбор железа

Скорость локальной генерации зависит в основном от пропускной способности VRAM, ширины шины памяти и того, помещается ли модель, а не от заявленных FLOPS. Эти примерные сочетания — отправная точка.

  • Моделям 7B–8B в Q4_K_M нужно около 6 ГБ VRAM. RTX 3060 или RTX 4060 8GB должны давать примерно 60–85 токенов в секунду.
  • Моделям 13B–14B в Q4_K_M нужно около 10 ГБ. RTX 4070 или RTX 3080 12GB должны выдавать примерно 40–60 токенов в секунду.
  • Моделям 32B–35B в Q4_K_M нужно около 22 ГБ. RTX 3090 или RTX 4090 24GB должны справляться примерно на 25–40 токенах в секунду.
  • Моделям 70B в Q4_K_M нужно около 48 ГБ, например две карты RTX 3090 или RTX 6000 Ada, примерно 15–25 токенов в секунду.

Что нужно для проброса GPU

В Linux установка обычных драйверов GPU на хост не даёт контейнерам доступ к GPU. Нужно установить официальный NVIDIA Container Toolkit, чтобы вызовы CUDA доходили до ядра изнутри контейнера.

На железе AMD используйте ROCm-тег образа (ollama/ollama:rocm) и пробросьте в контейнер устройства /dev/kfd и /dev/dri.

Docker Desktop в Windows с WSL2 добавляет собственные странности виртуального сетевого моста. Симптомы — недоступные контейнеры или ошибки сокетов, и обычно они указывают на проблемы маршрутизации между хостом и контейнером в этом мосту.

Пишем файл Compose

Сначала создайте рабочий каталог на сервере:

mkdir -p ~/local-ai && cd ~/local-ai

Дальше — docker-compose.yml. Текущие спецификации Compose объявляют ключ version верхнего уровня устаревшим, так что его не указываем. Первым идёт сервис Ollama:

services:
  ollama:
    image: ollama/ollama:0.6.5
    container_name: ollama
    restart: unless-stopped
    environment:
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_MAX_LOADED_MODELS=1
      - OLLAMA_NUM_PARALLEL=2
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_models:/root/.ollama
    networks:
      - ai_network
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

В том же файле следом идёт Open WebUI:

  open-webui:
    image: ghcr.io/open-webui/open-webui:v0.5.20
    container_name: open-webui
    restart: unless-stopped
    depends_on:
      - ollama
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - WEBUI_AUTH=true
      - WEBUI_SECRET_KEY=e8f901c23a4b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0
    ports:
      - "3000:8080"
    volumes:
      - open_webui_data:/app/backend/data
    networks:
      - ai_networkvolumes:
  ollama_models:
    name: ollama_models
  open_webui_data:
    name: open_webui_datanetworks:
  ai_network:
    name: ai_network
    driver: bridge

Несколько настроек в этих блоках стоит разобрать подробнее.

  • OLLAMA_KEEP_ALIVE=24h держит загруженные тензоры модели в VRAM во время долгих простоев, чтобы активным пользователям не приходилось ждать перезагрузки.
  • OLLAMA_MAX_LOADED_MODELS=1 разрешает занимать VRAM только одной модели за раз. Это предотвращает фрагментацию и падения из-за нехватки памяти.
  • 127.0.0.1:11434:11434 привязывает Ollama к loopback-адаптеру хоста. Внешний трафик API не видит, и до него добирается только Open WebUI через внутреннюю сеть Docker.
  • WEBUI_SECRET_KEY — сид для подписи сессионных JWT-cookie. Сгенерируйте надёжный ключ командой openssl rand -hex 32 перед первым запуском.

Закреплённые теги образов здесь важнее, чем может показаться. Плавающий тег вроде latest может поменяться под стеком за ночь, и перезапуск во вторник подтянет рантайм, который никто не тестировал. С фиксированными версиями пересборка через год даст тот же результат, что и сегодня, а обновление становится осознанной правкой одной строки, которую можно проверить и откатить.

Запуск и загрузка модели

Поднимите стек в фоне:

docker compose up -d

Проверьте состояние контейнеров и привязки портов:

docker compose ps

Скачайте первую модель, выполнив команду внутри контейнера Ollama:

docker compose exec ollama ollama pull llama3.2:8b

Убедитесь, что модель зарегистрирована вместе с квантизацией:

docker compose exec ollama ollama list

TLS перед всем

Порт 3000 никогда не должен смотреть в публичную сеть без TLS и маршрутизации по хосту. С этим хорошо справляется Caddy, поскольку он автоматически управляет HTTPS-сертификатами. Установите его на хост и напишите /etc/caddy/Caddyfile:

ai.yourdomain.com {
    reverse_proxy 127.0.0.1:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
    }
}

Перезагрузите Caddy, чтобы он получил сертификат и начал проксировать через шифрованное соединение:

sudo systemctl reload caddy

Больше, чем чат

Со временем командам нужны автономные многошаговые агенты и автоматизация процессов, а не только разговоры. Тогда Open WebUI может работать рядом с фреймворками оркестрации вроде Dify или рядом с высокопроизводительным бэкендом, например vLLM. Поскольку Ollama предоставляет стандартный REST API во внутренней сети, другие инструменты в том же мосту могут пользоваться его моделями напрямую.

Решение проблем

Частые проблемы с причинами и решениями.

  • WebUI показывает ошибку подключения к бэкенду. Причина — неверный OLLAMA_BASE_URL или несовпадение сетей между контейнерами. Убедитесь, что переменная совпадает с внутренним DNS-именем http://ollama:11434, затем проверьте доступность изнутри контейнера Open WebUI, сделав curl на путь /api/tags по имени хоста Ollama.
  • Ollama не видит CUDA GPU. Либо не установлен NVIDIA Container Toolkit, либо в compose-файле нет блока резервирования GPU. Установите nvidia-container-toolkit на хост, выполните sudo systemctl restart docker и проверьте, что резервирования соответствуют спецификации compose.
  • Токены в секунду со временем резко падают. Модель больше доступной VRAM, и слои вытесняются в системную RAM. Посмотрите распределение через nvidia-smi, затем перейдите на модель поменьше или более компактную квантизацию (Q4_K_M вместо Q8_0).
  • Permission denied (доступ запрещён) на томе open_webui_data. Права каталога на хосте не совпадают с UID процесса контейнера, равным 1000. Выполните sudo chown -R 1000:1000 /var/lib/docker/volumes/open_webui_data.
  • Обрывы WebSocket во время стриминга. Обратный прокси буферизует потоковые ответы или ломает запросы на апгрейд. Убедитесь, что он корректно пробрасывает заголовки Upgrade и Connection с выключенной буферизацией.

Бэкапы

Аккаунты пользователей, история чатов, RAG-индексы и конфигурация должны сохраняться. Веса моделей всегда можно скачать заново, поэтому не включайте их в бэкапы ради экономии места. Напишите безопасный, неразрушающий скрипт в /usr/local/bin/backup-local-ai.sh. Он начинается со строки интерпретатора и строгого режима:

#!/usr/bin/env bash
set -euo pipefail

Тело задаёт каталог бэкапов и имя файла с меткой времени, приостанавливает Open WebUI, архивирует том с данными и снова запускает контейнер:

BACKUP_DIR="/var/backups/local-ai"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
TARGET_FILE="${BACKUP_DIR}/open_webui_data_${TIMESTAMP}.tar.gz"mkdir -p "${BACKUP_DIR}"echo "Stopping Open WebUI container..."
docker stop open-webuiecho "Archiving Open WebUI database and user storage..."
tar -czf "${TARGET_FILE}" -C /var/lib/docker/volumes/open_webui_data/_data .echo "Restarting Open WebUI container..."
docker start open-webuiecho "Cleaning backups older than 14 days..."
find "${BACKUP_DIR}" -type f -name "*.tar.gz" -mtime +14 -deleteecho "Backup execution complete: ${TARGET_FILE}"

Сделайте его исполняемым и откройте crontab:

sudo chmod +x /usr/local/bin/backup-local-ai.sh
sudo crontab -e

Затем добавьте строку для ежедневного запуска в 3:00:

0 3 * * * /usr/local/bin/backup-local-ai.sh >> /var/log/local-ai-backup.log 2>&1

Итоговый чек-лист

  • Сетевая изоляция: порт Ollama 11434 привязан к loopback (127.0.0.1) и недоступен публично.
  • Аутентификация: в Open WebUI явно задано WEBUI_AUTH=true.
  • Надёжность ключа: настроен собственный 64-символьный WEBUI_SECRET_KEY.
  • Закрепление образов: образы используют закреплённые версии, а не плавающие теги.
  • Лимиты VRAM: keep-alive и ограничения параллельности настроены так, чтобы не ронять драйвер GPU.
  • Бэкапы: тома с базой данных архивируются ежедневно с хранением копий.

Для справки: основная документация находится в GitHub-репозитории Ollama, документации NVIDIA Container Toolkit и спецификации Docker Compose.