Головна / Статті / 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.