Владелец небольшого магазина отвечает на одни и те же вопросы: «есть ли в наличии», «сколько идёт доставка», «можно ли вернуть». Менеджер переписывается в трёх каналах, прайс живёт в Excel, а регламенты — в голове. Зарубежный чат-ассистент решает часть задач, но требует валютной карты и забирает переписку клиентов на чужой сервер.
Ниже — рабочая альтернатива: ассистент, который живёт на вашем VDS, знает ваш прайс и условия доставки, отвечает сотрудникам и не отправляет данные клиентов за границу. Всё собирается из трёх контейнеров и запускается за вечер.
Материал рассчитан на инженера-практика и владельца бизнеса «один за всех»: минимум теории, готовая конфигурация, лимиты ресурсов и разбор грабель, на которые наступают при первом запуске LLM на CPU.
1. Зачем магазину ассистент на своём сервере
Три сценария, которые окупают сервер
- Ответы по прайсу и условиям. Сотрудник спрашивает: «что ответить клиенту про возврат брака» — ассистент находит пункт в вашем регламенте и формулирует ответ. Без поиска по папкам и звонков.
- Черновики для поддержки. Входящее сообщение превращается в вежливый ответ, который менеджер только правит. Скорость ответа падает с 15 минут до минуты.
- Обучение стажёра. Новый сотрудник задаёт вопросы по процессам и получает ответы из вашей же базы знаний, а не из чужих статей.
Сколько это стоит и когда облако выгоднее
Свой ассистент — это не бесплатно: вы платите за VDS (обычно от 400–800 ₽ в месяц за конфигурацию с 4 ГБ RAM), электричество не считаем, зато нет подписки на каждого сотрудника и нет лимита сообщений. Ассистент работает круглосуточно и не зависит от курса.
Облачная подписка остаётся разумным выбором, если нужна топовая модель для сложного кода, десятки пользователей одновременно или генерация изображений. Для типовых задач магазина — прайс, доставка, возвраты, регламенты — локальной модели хватает с запасом.
Приватность: переписка клиентов — это персональные данные
Обращения клиентов содержат имена, адреса, телефоны, номера заказов. Отправлять их в зарубежный сервис без правовых оснований — риск для бизнеса. Локальный ассистент держит данные внутри вашего контура: запросы, история чатов и загруженные документы остаются на диске вашего VDS, который вы контролируете и можете стереть в любой момент.
Совет инженера: начните с одного сценария
Не пытайтесь сразу заменить всю поддержку. Возьмите один тип вопросов — например, «условия доставки» — и добейтесь, чтобы ассистент отвечал по нему без ошибок. Расширять базу знаний проще, когда есть работающий эталон.
2. Архитектура: Ollama, Open WebUI и база знаний
Система состоит из трёх независимых частей. Такое разделение позволяет обновлять интерфейс, не перекачивая модели, и наоборот.
| Компонент | Роль | Образ | Данные |
|---|---|---|---|
| Ollama | Загружает модель в память и считает токены. Отдаёт HTTP API на порту 11434. |
ollama/ollama:0.40.6 |
./data/ollama — веса моделей |
| Open WebUI | Веб-интерфейс: аккаунты, история чатов, загрузка документов, поиск по базе знаний (RAG). Слушает 8080. |
openwebui/open-webui:0.12.3 |
./data/open-webui — база чатов и файлы |
| Caddy | Принимает HTTPS на 80/443 и проксирует запросы в интерфейс. Сертификат выпускает сам. | caddy:2.11.7 |
./data/caddy — сертификаты |
Как проходит запрос
- Сотрудник открывает
https://ai.вашдомен.ру— трафик терминирует Caddy. - Open WebUI собирает контекст: историю диалога и фрагменты ваших документов, найденные по векторному индексу.
- Запрос уходит в Ollama по внутренней сети Docker на
http://ollama:11434. Наружу этот порт не публикуется вообще. - Модель генерирует ответ, Open WebUI сохраняет чат в свою базу и отдаёт его в браузер.
Отдельная модель-эмбеддер (bge-m3, 1.16 ГБ) превращает абзацы прайса в векторы. Без неё поиск по документам не работает: модель не «читает» весь прайс на каждый вопрос, она получает только релевантные куски.
3. Требования к VDS и выбор модели под CPU
Модель — главный потребитель памяти. Вес скачанных файлов известен заранее, поэтому конфигурацию сервера считают от неё, а не наоборот.
| Конфигурация | vCPU / RAM / Диск | Модель | Кому подходит |
|---|---|---|---|
| Минимальная | 2 vCPU • 4 ГБ RAM + swap 2 ГБ • 40 ГБ NVMe | qwen3:4b — 2.50 ГБ |
1–3 сотрудника, короткие ответы по прайсу |
| Рекомендуемая | 4 vCPU • 8 ГБ RAM • 80 ГБ NVMe | qwen3:8b — 5.23 ГБ |
Магазин с базой знаний и 3–10 сотрудниками |
К весу модели добавьте базу знаний: эмбеддер bge-m3 занимает 1.16 ГБ на диске и ещё около 0.6 ГБ оперативной памяти, пока считает векторы. Поэтому в минимальной конфигурации 40 ГБ диска — это нижняя граница, а не запас.
Честно про 4 ГБ: при лимите 4 ГБ у Ollama и 1 ГБ у интерфейса часть памяти уйдёт в swap. Это рабочий, но медленный режим — для первого знакомства и одного-двух сотрудников. Если ассистент станет ежедневным инструментом, 8 ГБ перестанут быть роскошью.
Почему CPU — это нормально
GPU ускоряет генерацию, но для чата поддержки он не обязателен: один сотрудник ждёт ответ 10–30 секунд и всё равно правит текст. CPU-инференс медленнее, зато не требует видеокарты, драйверов и отдельного тарифа. На старте берите CPU-сервер, а GPU добавляйте, если ассистентом начнут пользоваться одновременно десятки человек.
Важное предупреждение: не выбирайте модель «на глаз»
Возьмите 15–20 реальных вопросов вашей поддержки и прогоните их через две модели: qwen3:4b (2.50 ГБ) и gemma3:4b (3.35 ГБ). Победителя определяйте по числу ответов, которые не пришлось переписывать. Красивые демо в интернете к вашему прайсу отношения не имеют.
Что ещё понадобится
- Домен или поддомен — например
ai.вашдомен.ру. Caddy выпустит на него сертификат автоматически, если A-запись уже указывает на IP сервера. - Swap. Без файла подкачки модель на 4 ГБ RAM будет убита OOM-killer при первом же длинном контексте.
- Свободные 80/443. Если на сервере уже работает Nginx или другой прокси — освободите порты или используйте существующий прокси вместо Caddy.
4. Подготовка сервера: Docker, swap и каталоги
Все команды выполняются от пользователя с sudo на свежем Ubuntu 24.04 LTS. Сначала — файл подкачки: он страхует от внезапного убийства контейнера с моделью.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf
sudo sysctl --system
free -h
Затем Docker Engine и Compose v2 из официального репозитория:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker compose version
Создайте каталог проекта и подкаталоги для данных. Модели и база чатов будут лежать рядом с конфигурацией — так их проще бэкапить и переносить на другой сервер.
sudo mkdir -p /opt/ai-assistant/data/{ollama,open-webui,caddy/data,caddy/config}
sudo chown -R $USER:$USER /opt/ai-assistant
cd /opt/ai-assistant
openssl rand -hex 32
Последняя команда печатает секрет для подписи сессий — он понадобится в .env. Значение обязательно: Open WebUI проверяет его на старте и с пустым секретом просто не запустится.
5. Манифест Docker Compose: три сервиса и лимиты
Ключевое решение манифеста: наружу смотрят только 80 и 443 порты Caddy. Ollama и Open WebUI не публикуют порты вообще — они общаются по внутренней сети Docker, а снаружи доступен лишь HTTPS-интерфейс. Это снимает целый класс проблем: API модели нельзя дёрнуть напрямую, даже если вы забыли про firewall.
services:
ollama:
image: ollama/ollama:0.40.6
container_name: ollama
restart: unless-stopped
environment:
OLLAMA_KEEP_ALIVE: "30m"
OLLAMA_MAX_LOADED_MODELS: "2"
OLLAMA_CONTEXT_LENGTH: "8192"
volumes:
- ./data/ollama:/root/.ollama
mem_limit: 4g
cpus: 2.0
open-webui:
image: openwebui/open-webui:0.12.3
container_name: open-webui
restart: unless-stopped
depends_on:
- ollama
environment:
OLLAMA_BASE_URL: "http://ollama:11434"
WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY}"
ENABLE_SIGNUP: "false"
ENABLE_INITIAL_ADMIN_SIGNUP: "true"
DEFAULT_USER_ROLE: "pending"
RAG_EMBEDDING_ENGINE: "ollama"
RAG_EMBEDDING_MODEL: "bge-m3"
volumes:
- ./data/open-webui:/app/backend/data
mem_limit: 1g
cpus: 1.0
caddy:
image: caddy:2.11.7
container_name: caddy
restart: unless-stopped
depends_on:
- open-webui
ports:
- "80:80"
- "443:443"
environment:
DOMAIN: "${DOMAIN}"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data/caddy/data:/data
- ./data/caddy/config:/config
mem_limit: 256m
cpus: 0.5
Файл .env рядом с манифестом — два значения, никаких секретов в репозитории:
DOMAIN=ai.example.ru
WEBUI_SECRET_KEY=вставьте_вывод_openssl_rand_hex_32
Что означают директивы
| Директива | Зачем нужна |
|---|---|
mem_limit: 4g | Потолок памяти для Ollama: 2.50 ГБ языковой модели, около 0.6 ГБ эмбеддера и KV-кэш контекста 8192. Без лимита контейнер выест всю RAM и утянет за собой систему. |
cpus: 2.0 | Ограничение ядер. Инференс не должен забирать 100% CPU у веб-сервера магазина, если он живёт на той же машине. |
OLLAMA_MAX_LOADED_MODELS | Две модели: языковая и эмбеддер базы знаний. С лимитом 1 Ollama выгружал бы qwen3, чтобы загрузить bge-m3, и каждый RAG-запрос платил бы за это десятками секунд. |
OLLAMA_CONTEXT_LENGTH | 8192 токена: системный промпт, история диалога и 3–5 фрагментов прайса. При 4096 RAG-ответы начинают терять цитаты; если памяти мало — снижайте до 4096 осознанно. |
ENABLE_SIGNUP | Свободная регистрация закрыта: аккаунты создаёт администратор. |
ENABLE_INITIAL_ADMIN_SIGNUP | Разрешает создать первого администратора при закрытой регистрации. После настройки выключите. |
DEFAULT_USER_ROLE | Новые пользователи попадают в pending и не видят чужие чаты, пока их не подтвердят. |
RAG_EMBEDDING_ENGINE | Эмбеддинги считает локальная Ollama, а не внешний сервис: документы не покидают сервер. |
Совет инженера: на 8 ГБ RAM поднимите лимиты
Возьмите модель qwen3:8b (5.23 ГБ), поставьте mem_limit: 7g для Ollama и 2.0 CPU. Остальные директивы не меняются: две загруженные модели и контекст 8192 укладываются в 7 ГБ с запасом. Не забудьте скачать новую модель — раздел 7 показывает как.
Для сверки: Open WebUI 0.12.3 и Ollama 0.40.6 — стабильные релизы от 16 октября 2026 года (у Ollama кандидат 0.40.6-rc1 публиковался отдельным тегом и в манифест не попал), Caddy 2.11.7. Если вы обновляете статью спустя месяцы, сверяйте теги на страницах релизов, а не берите из текста.
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud — VDS для локальных моделей
Высокочастотные ядра и NVMe: веса моделей читаются с диска, поэтому накопитель влияет на скорость ответа не меньше процессора.
Selectel — Гарантированные ресурсы под LLM
Гарантированные vCPU и память в дата-центрах Tier III — конфигурация под модель qwen3:8b и базу знаний магазина.
Beget — Быстрый старт для магазина
Понятная панель и быстрый старт: подойдёт, чтобы проверить сценарии ассистента прежде, чем переносить его на выделенный сервер.
6. HTTPS и защита периметра: Caddy 2.11.7 и UFW
Caddy выпускает сертификат Let's Encrypt сам, без certbot и cron. Единственное условие — A-запись домена уже указывает на IP сервера, а порты 80 и 443 свободны.
{$DOMAIN} {
encode zstd gzip
reverse_proxy open-webui:8080 {
flush_interval -1
}
}
Директива flush_interval -1 отключает буферизацию ответа. Без неё стриминг токенов в чате превращается в «ответ появляется целиком через 30 секунд»: Caddy копит ответ и отдаёт его одним куском.
Обратите внимание: адрес апстрима — open-webui:8080, имя контейнера во внутренней сети Docker, а не 127.0.0.1. Внутри контейнера Caddy петля localhost указывает на него самого, поэтому «локальный» адрес здесь не работает.
cd /opt/ai-assistant
nano Caddyfile
docker compose up -d
docker compose logs -f caddy
В логах появится получение сертификата. Через минуту интерфейс открывается по https://ai.example.ru. Проверить сертификат и цепочку можно чекером SSL/TLS — он покажет срок действия и издателя.
Firewall: что открывать
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Важное предупреждение: UFW не фильтрует опубликованные порты Docker
Docker вставляет свои правила в iptables раньше цепочки UFW, поэтому любой порт, опубликованный через ports:, становится доступен из интернета независимо от ufw deny. В нашем манифесте опубликованы только 80 и 443, а 11434 и 8080 не публикуются вовсе — именно поэтому API модели недоступен снаружи. Не добавляйте ports к Ollama «на время отладки» на боевом сервере.
Убедиться, что порт модели закрыт, можно сканером открытых портов: проверьте 11434 и 8080 по внешнему IP — оба должны быть закрыты.
7. Первый запуск: модель, администратор и закрытие регистрации
Контейнеры запущены, но моделей ещё нет — Ollama скачивает их по требованию. Скачайте языковую модель и эмбеддер для базы знаний:
cd /opt/ai-assistant
docker compose exec ollama ollama pull qwen3:4b
docker compose exec ollama ollama pull bge-m3
docker compose exec ollama ollama list
Первая команда скачает 2.50 ГБ, вторая — 1.16 ГБ. На канале 100 Мбит/с это несколько минут; следите за свободным местом.
Создание администратора
- Откройте
https://ai.example.ru. Появится форма регистрации — она разрешена только для первого администратора, остальные регистрации закрыты. - Зарегистрируйте аккаунт с рабочей почтой и надёжным паролем. Сгенерируйте его генератором паролей, а не придумывайте.
- Выберите модель
qwen3:4bв селекторе и задайте первый вопрос — например, «какие условия возврата товара надлежащего качества».
Сразу после создания администратора выключите bootstrap-регистрацию: иначе форму сможет открыть кто угодно, кто первым до неё дотянется после перезапуска.
sed -i 's/ENABLE_INITIAL_ADMIN_SIGNUP: "true"/ENABLE_INITIAL_ADMIN_SIGNUP: "false"/' docker-compose.yml
docker compose up -d open-webui
docker compose logs --tail=30 open-webui
Дальше аккаунты сотрудников создаёт администратор в панели управления. Новые пользователи получают роль pending и не видят чужие диалоги, пока вы их не подтвердите.
Совет инженера: проверьте, что модель действительно локальная
Посмотрите docker stats во время генерации и убедитесь, что в настройках Open WebUI не добавлены внешние провайдеры. На тестовой машине можно ужесточить исходящие правила (sudo ufw default deny outgoing), но на боевом сервере так делать нельзя: Caddy не сможет продлить сертификат Let’s Encrypt.
8. База знаний магазина: прайс, регламенты и RAG
Без документов ассистент отвечает «в общем», опираясь на знания модели. С документами — цитирует ваши условия. Технически это RAG: файлы режутся на фрагменты, эмбеддер bge-m3 превращает их в векторы, и на каждый вопрос модель получает только релевантные куски.
- Откройте раздел Workspace → Knowledge и создайте коллекцию, например «Прайс и регламенты».
- Загрузите файлы: прайс в CSV или XLSX, регламент возвратов в DOCX, условия доставки в PDF. Текстовый слой обязателен — сканы без распознавания проиндексируются пустыми.
- Дождитесь окончания обработки: у каждого файла появится статус. Первый запуск дольше обычного, потому что эмбеддер
bge-m3загружается в память. - Привяжите коллекцию к модели в Workspace → Models или к отдельному чату. Так сотрудники не забудут включить базу знаний.
Совет инженера: один документ — одна тема
Не складывайте в один файл прайс, договор и скрипты продаж: фрагменты перемешаются, и модель начнёт цитировать не то. Отдельные файлы по темам дают заметно более точные ответы при том же объёме данных.
Как проверить качество поиска
Задайте 10 вопросов, ответы на которые вы знаете наизусть: конкретная цена, срок доставки в город, условие возврата. Если ассистент путает цифры — причина почти всегда в нарезке: длинные таблицы плохо делятся на фрагменты. Помогает разбить прайс на файлы по категориям товаров и увеличить число подтягиваемых фрагментов (RAG_TOP_K в настройках документов).
Важное предупреждение: не загружайте лишнее
База знаний доступна всем подтверждённым пользователям интерфейса. Договоры с персональными данными клиентов, пароли, токены и внутренние финансовые документы там не место. Для сканов без текстового слоя сначала прогоните распознавание — как это сделать, разобрано в материале про электронный архив с OCR.
9. Эксплуатация: бэкапы, обновления и контроль диска
Самое ценное в системе — не контейнеры, а каталог ./data/open-webui: база чатов, загруженные документы и настройки. Модели в ./data/ollama восстанавливаются повторной загрузкой, поэтому в бэкап их можно не включать.
Бэкап базы чатов
cd /opt/ai-assistant
mkdir -p backups
docker compose stop open-webui
tar czf backups/webui-$(date +%F).tar.gz -C data/open-webui .
docker compose start open-webui
ls -lh backups
Останавливать контейнер нужно потому, что внутри SQLite: копирование файла на живой базе даёт битую копию. Простой занимает секунды. Раз в неделю копируйте архив на другой сервер или в объектное хранилище — локальный бэкап не спасает от потери диска.
Обновления без сюрпризов
Теги в манифесте зафиксированы: ollama/ollama:0.40.6, openwebui/open-webui:0.12.3, caddy:2.11.7. Плавающих latest нет, поэтому обновление — это осознанная смена версии, а не лотерея после перезагрузки.
cd /opt/ai-assistant
docker compose pull
docker compose up -d
docker compose ps
Порядок такой: прочитайте release notes новой версии, обновите сначала на тестовом сервере, сделайте бэкап и только потом меняйте тег на боевом. Откат — вернуть предыдущий тег и выполнить docker compose up -d.
Контроль ресурсов
docker stats --no-stream
du -sh data/ollama data/open-webui
docker system df
Если data/ollama растёт быстрее ожидаемого — вы скачали лишние модели: удалите ненужные через docker compose exec ollama ollama rm <модель>. Лимиты памяти и CPU в манифесте стоит пересматривать при смене модели, а не «на глаз» — подробнее в разборе лимитов Docker Compose.
Что дальше: автоматизация ответов
Когда ассистент начнёт уверенно отвечать по прайсу, следующий шаг — связать его с каналами: заявки из Telegram и почты, черновики ответов, эскалация сложных вопросов. Это делается в n8n на своём VDS: сценарий вызывает локальную модель по API внутри Docker-сети и не отправляет данные наружу.
Разверните ассистента на своём сервере
Для минимальной конфигурации хватит VDS с 2 vCPU и 4 ГБ RAM, для магазина с базой знаний — 4 vCPU и 8 ГБ. Возьмите тариф с NVMe: веса моделей читаются с диска, и на медленном накопителе ответы заметно тормозят.
Подобрать VDS под локальную модель