Подключение локальной LLM к интернету: Docker, Ministral, Traefik | AiManual
AiManual Logo Ai / Manual.
14 Янв 2026 Гайд

Заблокированная нейросеть: как дать локальной LLM доступ в интернет через Docker и Ministral

Практический гайд: даем локальной нейросети доступ к веб-страницам и актуальным данным через Docker, Ministral и обратный прокси. Пошаговая инструкция с примера

Почему ваша локальная нейросеть глупа без интернета

Вы скачали 7-миллиардную модель, запустили ее в Ollama или LM Studio, и она бодро отвечает на вопросы про философию Канта. Но стоит спросить "Какая погода в Москве?" или "Что пишут на Хабре про Docker?", и вы получаете либо бред, либо вежливый отказ. Типичный ответ: "Как локальная модель, я не имею доступа к актуальным данным из интернета". Звучит знакомо?

Это главная проблема локальных LLM. Они заморожены во времени, как муха в янтаре. Дата их последнего обновления - это дата тренировочного сета. Для реальной работы с актуальной информацией им нужен доступ в интернет. Но просто открыть порт и сказать "иди погуляй" - не вариант. Безопасность, контроль, стабильность - все это рушится.

Министр (Ministral) - это не конкретная модель, а подход: легковесная LLM в контейнере с инструментами для работы с внешним миром. Мы будем использовать Mistral 7B как пример, но принципы работают с любой локальной моделью.

Что мы на самом деле строим: не просто доступ, а контролируемый шлюз

Проблема в том, что большинство гайдов предлагают либо запустить модель с флагом --network=host (кошмар безопасности), либо настроить сложные VPN-туннели (кошмар администрирования). Мы пойдем другим путем: создадим изолированную среду, где модель может безопасно запрашивать внешние ресурсы через контролируемый прокси.

Архитектура выглядит так:

  • Локальная LLM (Mistral 7B) в Docker-контейнере
  • Traefik как обратный прокси и роутер
  • Изолированная Docker-сеть для коммуникации
  • Инструменты для веб-скрапинга внутри контейнера

Ключевая идея: модель не "выходит в интернет" напрямую. Она отправляет запросы к специальным endpoint'ам, которые уже обрабатывают веб-запросы и возвращают чистый контент.

1 Подготовка: почему Docker, а не голый Python

Я знаю, о чем вы думаете: "Зачем городить Docker, если можно просто установить requests и BeautifulSoup?". Потому что через месяц вы забудете, какие зависимости установили, а через два - почему скрипт перестал работать. Docker дает предсказуемость и изоляцию.

Сначала установите Docker Desktop если у вас Windows/Mac, или просто Docker Engine на Linux. Для пользователей WSL2 - включите интеграцию в настройках Docker Desktop.

# Проверяем установку
$ docker --version
Docker version 24.0.7, build afdd53b

# Проверяем, что Docker Daemon работает
$ docker run hello-world

# Если видите "Hello from Docker!" - все готово

Внимание пользователям WSL2: не запускайте Docker внутри WSL без Docker Desktop. Интеграция через Docker Desktop дает стабильную работу с сетью и volumes. Самый частый грабель - конфликт сетевых драйверов.

2 Dockerfile: собираем контейнер с мозгами и руками

Создаем директорию проекта и пишем Dockerfile. Мы не просто упаковываем модель - мы даем ей инструменты для работы с внешним миром.

# Dockerfile
FROM python:3.11-slim

# Устанавливаем системные зависимости для сборки
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Устанавливаем инструменты для работы с интернетом
RUN pip install --no-cache-dir \
    requests \
    beautifulsoup4 \
    markdownify \
    fastapi \
    uvicorn \
    python-multipart \
    ollama

# Создаем рабочую директорию
WORKDIR /app

# Копируем скрипты
COPY web_tools.py .
COPY app.py .

# Открываем порт для FastAPI
EXPOSE 8000

# Запускаем приложение
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Теперь создаем web_tools.py - набор инструментов, которые наша модель будет использовать через LangChain или прямое вызовы:

# web_tools.py
import requests
from bs4 import BeautifulSoup
from markdownify import markdownify as md
import json

class WebTools:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (compatible; LocalLLM-Bot/1.0)'
        })
    
    def fetch_url(self, url: str) -> str:
        """Получает содержимое URL и конвертирует в Markdown"""
        try:
            response = self.session.get(url, timeout=10)
            response.raise_for_status()
            
            # Парсим HTML
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # Удаляем ненужные элементы
            for element in soup(['script', 'style', 'nav', 'footer']):
                element.decompose()
            
            # Конвертируем в markdown
            text_content = md(str(soup))
            return text_content[:5000]  # Ограничиваем объем
            
        except Exception as e:
            return f"Ошибка при получении {url}: {str(e)}"
    
    def search_duckduckgo(self, query: str) -> str:
        """Ищет через DuckDuckGo Instant Answer API"""
        try:
            response = self.session.get(
                'https://api.duckduckgo.com/',
                params={'q': query, 'format': 'json', 'no_html': '1'}
            )
            data = response.json()
            
            result = []
            if data.get('AbstractText'):
                result.append(f"Краткий ответ: {data['AbstractText']}")
            if data.get('RelatedTopics'):
                for topic in data['RelatedTopics'][:3]:
                    if 'Text' in topic:
                        result.append(topic['Text'])
            
            return '\n'.join(result) if result else 'Информация не найдена'
            
        except Exception as e:
            return f"Ошибка поиска: {str(e)}"

3 FastAPI-сервер: мост между моделью и внешним миром

Теперь создаем app.py - FastAPI сервер, который будет принимать запросы от модели и выполнять веб-запросы:

# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from web_tools import WebTools
import subprocess
import json

app = FastAPI(title="Local LLM Internet Gateway")
tools = WebTools()

class WebRequest(BaseModel):
    url: str

class SearchRequest(BaseModel):
    query: str

class LLMRequest(BaseModel):
    prompt: str
    use_web: bool = False

@app.get("/health")
def health_check():
    return {"status": "healthy", "service": "llm-gateway"}

@app.post("/fetch")
def fetch_web_content(request: WebRequest):
    """Получить содержимое веб-страницы"""
    content = tools.fetch_url(request.url)
    return {"url": request.url, "content": content}

@app.post("/search")
def search_web(request: SearchRequest):
    """Поиск информации в интернете"""
    results = tools.search_duckduckgo(request.query)
    return {"query": request.query, "results": results}

@app.post("/ask")
async def ask_llm(request: LLMRequest):
    """Задать вопрос LLM с возможностью поиска в интернете"""
    
    # Если нужен поиск в интернете
    if request.use_web and "погода" in request.prompt.lower():
        search_result = tools.search_duckduckgo(request.prompt)
        enhanced_prompt = f"Вопрос: {request.prompt}\nКонтекст из интернета: {search_result}"
    else:
        enhanced_prompt = request.prompt
    
    # Вызываем локальную LLM через Ollama
    try:
        result = subprocess.run(
            ['ollama', 'run', 'mistral', enhanced_prompt],
            capture_output=True,
            text=True,
            timeout=30
        )
        
        if result.returncode == 0:
            return {"response": result.stdout.strip()}
        else:
            return {"error": result.stderr}
            
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

4 Docker Compose: оркестрация всего зоопарка

Один контейнер - это скучно. Давайте запустим полноценный стек с Traefik в качестве обратного прокси:

# docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    container_name: llm-gateway-proxy
    command:
      - --api.insecure=true
      - --providers.docker=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"  # Dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - llm-network

  llm-gateway:
    build: .
    container_name: llm-internet-gateway
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.llm-gateway.rule=Host(`llm.localhost`)"
      - "traefik.http.routers.llm-gateway.entrypoints=web"
      - "traefik.http.services.llm-gateway.loadbalancer.server.port=8000"
    volumes:
      - ./models:/app/models
    environment:
      - OLLAMA_HOST=host.docker.internal
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - llm-network
    depends_on:
      - traefik

networks:
  llm-network:
    driver: bridge
💡
Зачем Traefik, если можно просто пробросить порт? Traefik дает автоматическое SSL, балансировку нагрузки, health checks и красивый dashboard. Когда вы добавите вторую модель или инструмент, вы оцените эту гибкость.

5 Запуск и тестирование: момент истины

Собираем и запускаем:

# Собираем образ
$ docker-compose build

# Запускаем стек
$ docker-compose up -d

# Проверяем, что все запустилось
$ docker-compose ps
NAME                      STATUS              PORTS
llm-gateway-proxy        Up 2 minutes        0.0.0.0:80->80/tcp
llm-internet-gateway     Up 2 minutes        8000/tcp

# Проверяем health check
$ curl http://localhost/health
{"status":"healthy","service":"llm-gateway"}

Теперь тестируем доступ в интернет через нашу шлюз:

# Получаем содержимое страницы
$ curl -X POST http://localhost/fetch \
  -H "Content-Type: application/json" \
  -d '{"url":"https://habr.com/ru/articles/"}' \
  | jq '.content | .[0:200]'

И самое интересное - спрашиваем у модели с использованием интернета:

# Задаем вопрос с поиском в интернете
$ curl -X POST http://localhost/ask \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Какая погода в Москве сегодня?","use_web":true}' \
  | jq '.response'

Ошибки, которые совершают все (и как их избежать)

Ошибка Почему происходит Как исправить
Connection refused от Ollama Контейнер не видит host.docker.internal В Docker Compose добавить extra_hosts и установить Ollama на хосте
SSL ошибки при запросах Устаревшие CA сертификаты в контейнере В Dockerfile: RUN apt-get update && apt-get install -y ca-certificates
Traefik не видит контейнер Отсутствует volume с docker.sock Проверить, что /var/run/docker.sock правильно подключен
Медленные ответы от модели Контейнеру не хватает ресурсов В docker-compose.yml добавить deploy.resources.limits

Что дальше: от простого шлюза к полноценной инфраструктуре

Сейчас у вас работает базовый шлюз. Но настоящая магия начинается, когда вы интегрируете это с другими инструментами:

  • LangChain: Создайте CustomTool для веб-поиска и добавьте в цепочку
  • RAG с актуальными данными: Используйте полученный контент для пополнения векторной базы
  • Автоматизация: Настройте периодический сбор данных с любимых сайтов
  • Безопасность: Добавьте авторизацию и rate limiting в Traefik middleware

Если вы хотите пойти дальше, посмотрите мой гайд про идеальный стек для self-hosted LLM, где я показываю, как подключить локальную модель к IDE и CLI-инструментам.

Помните: эта архитектура - компромисс между безопасностью и функциональностью. Модель все еще изолирована, но может запрашивать данные через контролируемые endpoint'ы. Это лучше, чем давать ей прямой доступ в интернет.

Самый частый вопрос: а зачем все это, если есть ChatGPT?

Потому что контроль. Потому что приватность. Потому что стоимость. Когда вы отправляете запрос в облако, вы не контролируете, что происходит с вашими данными. Когда вы платите за API-вызовы, каждый вопрос имеет цену. Когда вы зависите от доступности чужого сервиса, вы зависите от его прихотей.

Локальная LLM с доступом в интернет - это золотая середина. Вы получаете актуальность облачных моделей с приватностью локальных. И самое главное - вы учитесь строить системы, а не просто потреблять сервисы.

Теперь ваша модель не просто архив знаний. Она живой инструмент, который может читать новости, проверять факты, анализировать тренды. И все это - не выходя из вашей Docker-сети.