Каждый веб-разработчик и сисадмин сталкивается с проблемой организации поиска по сайту: стандартный поиск в SQL через операторы LIKE '%...%' или встроенные индексы в PostgreSQL/MySQL работают медленно, не прощают опечаток и не поддерживают мгновенную выдачу подсказок на лету. При этом классический стек Elasticsearch или OpenSearch требует минимум 4–8 ГБ оперативной памяти и неподъемен для бюджетных VDS.
Современным решением стал Meilisearch — легковесный поисковый движок на языке Rust. Он потребляет от 50–150 МБ RAM, оптимизирован под выдачу подсказок прямо во время набора символов («search-as-you-type») и поддерживает исправление опечаток и русский язык прямо из коробки. В этом руководстве мы развернем Meilisearch в Docker Compose с разграничением прав API, настроим защищенный Nginx Reverse Proxy и покажем интеграцию живого поиска на сайт.
1. Когда Meilisearch эффективнее Elasticsearch: границы применимости
Elasticsearch и OpenSearch остаются признанными отраслевыми лидерами для распределенной аналитики, полнотекстовой агрегации терабайтов логов (стек ELK) и сложных корпоративных хранилищ. Однако для типового сайта, базы знаний или интернет-магазина на 10 000 – 100 000 товаров стек на базе JVM избыточен: он требует от 2–4 ГБ оперативной памяти только под базовый Heap и сложной ручной конфигурации анализаторов морфологии.
Meilisearch решает принципиально другую задачу — обеспечивает идеальный пользовательский опыт «живого поиска» (End-User Search Experience). Движок использует встраиваемую базу данных LMDB (Lightning Memory-Mapped Database), взаимодействует с памятью через системный механизм mmap ядра Linux без виртуальной машины Java и оптимизирован под работу на компактных серверах с 1–2 vCPU.
| Критерий | Elasticsearch / OpenSearch | Meilisearch (Rust) |
|---|---|---|
| Потребление RAM | От 2–4 ГБ только под базовый JVM Heap (рекомендуется от 8 ГБ) | От 50–150 МБ для каталогов до 50 000 документов |
| Толерантность к опечаткам | Требует сложной настройки Fuzzy Query и кастомных анализаторов | Включена по умолчанию (1–2 опечатки без падения скорости) |
| Скорость старта службы* | Обычно до 45–90 секунд на инициализацию кластера и JVM | Менее 1–2 секунд в контейнере Docker |
| Поддержка языков | Установка внешних плагинов и словарей стемминга | Встроенный токенизатор Charabia с поддержкой кириллицы из коробки |
| Интеграция на фронтенд | Только через промежуточный бэкенд (прямой доступ небезопасен) | Прямые запросы из браузера через изолированный Search API Key |
* Примечание: показатели времени старта и потребления памяти приведены как ориентировочные на основе типовых конфигураций одиночных узлов.
Редакции Meilisearch: Community vs Enterprise
Проект поставляется в двух версиях:
- Community Edition (лицензия MIT): полностью открытая редакция без каких-либо функциональных ограничений для одиночных серверов. Именно она используется в стандартном официальном Docker-образе и покрывает 100% потребностей веб-проектов и интернет-магазинов.
- Enterprise Edition (лицензия BUSL 1.1): коммерческая редакция для крупных кластеров, включающая автоматическое шардирование данных и нативные бэкапы напрямую в S3.
Если вы создаете легковесный бэкенд для мобильных или веб-приложений, Meilisearch отлично дополняет базы данных SQLite и BaaS-платформы, как описано в руководстве по развертыванию PocketBase на VDS.
2. Архитектура безопасности: Master Key, Admin Key и публичный Search API
Главное архитектурное удобство Meilisearch для веб-проектов — возможность отправлять поисковые запросы напрямую из браузера пользователя в поисковый движок без обязательного проксирования через PHP, Node.js или Python. Это разгружает сервер приложений и снижает время ожидания ответа.
Чтобы такая схема была абсолютно безопасной, в Meilisearch реализована трехуровневая модель авторизации:
Master Key (MEILI_MASTER_KEY): суперсекретный мастер-ключ, обладающий наивысшими правами ко всем операциям и эндпоинтам системы. По лучшим практикам безопасности он используется исключительно системным администратором для генерации и ротации прикладных токенов через эндпоинт/keys. Публикация этого ключа в коде приложений или клиентском JS строго запрещена.Default Admin API Key: создается автоматически при первом старте. Обладает полным набором действий (actions: ["*"]), включая добавление, обновление и удаление документов, создание индексов и настройку ранжирования. Этот ключ хранится строго на сервере в бэкенд-приложениях или CLI-скриптах импорта.Default Search API Key: изолированный ключ, наделенный по официальной спецификации правами строго на поиск (actions: ["search"]). С ним разрешены только поисковые запросы к эндпоинтам/indexes/*/searchи/multi-search. Он не позволяет читать документы по ID (эндпоинт/documents), модифицировать данные или просматривать настройки. Именно этот ключ безопасно передавать во фронтенд для клиентских скриптов.
При установке флага MEILI_ENV=production движок Meilisearch строго требует, чтобы мастер-ключ содержал не менее 16 байт (символов). Если передать более короткий ключ или не указать его вовсе, контейнер завершит работу с ошибкой валидации конфигурации при старте.
Сгенерировать надежный мастер-ключ длиной 32 байта можно стандартной консольной командой Linux:
# Генерация надежного случайного ключа в Base64
openssl rand -base64 32
# Пример вывода:
# vR8p9mK2zQ1xL7wN4jF5tA6sD8yC0eB3gH1uI4oP7qE=
Для быстрого создания случайных секретов в браузере вы также можете использовать наш генератор надежных паролей и токенов SysKit.
3. Развертывание Meilisearch в Docker Compose с лимитами ресурсов
Для развертывания Meilisearch мы используем изолированную директорию /opt/meilisearch, файл секретов .env и манифест docker-compose.yml. Мы обязательно привязываем порт службы к локальному интерфейсу 127.0.0.1, чтобы защитить порт 7700 от прямого доступа из интернета и автоматических сетевых сканеров.
Подготовка окружения и .env
# Создаем рабочую директорию проекта
sudo mkdir -p /opt/meilisearch && cd /opt/meilisearch
# Генерируем мастер-ключ и сохраняем его в изолированный файл переменных
echo "MEILI_MASTER_KEY=$(openssl rand -base64 32)" | sudo tee .env
sudo chmod 600 .env
Манифест docker-compose.yml с Health Check и лимитами
В конфигурации мы фиксируем актуальный проверенный релиз getmeili/meilisearch:v1.54, задаем внутренний адрес прослушивания MEILI_HTTP_ADDR, отключаем телеметрию (MEILI_NO_ANALYTICS=true) и согласовываем лимиты индексации с ограничениями cgroups:
services:
meilisearch:
# Фиксация конкретного стабильного релиза (проверяйте актуальный тег на GitHub Releases)
image: getmeili/meilisearch:v1.54
container_name: meilisearch
restart: unless-stopped
env_file: .env
environment:
- MEILI_ENV=production
- MEILI_HTTP_ADDR=0.0.0.0:7700
- MEILI_NO_ANALYTICS=true
# Лимит памяти под рабочие буферы при индексации (формат MB/GiB)
- MEILI_MAX_INDEXING_MEMORY=500MB
- MEILI_MAX_INDEXING_THREADS=1
ports:
# Защита от обхода UFW: привязка строго к локальному хосту
- "127.0.0.1:7700:7700"
volumes:
- meili_data:/meili_data
# Мониторинг жизнеспособности службы через команду оболочки
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:7700/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
# Лимиты cgroups: mem_limit должен быть выше MEILI_MAX_INDEXING_MEMORY
mem_limit: 768m
cpus: 1.0
volumes:
meili_data:
driver: local
Во время добавления больших пакетов документов Meilisearch перестраивает деревья b-tree. Значение MEILI_MAX_INDEXING_MEMORY=500MB ограничивает именно временную память построения индекса. Общий лимит контейнера mem_limit: 768m задается с обязательным запасом (250+ МБ), чтобы базовый RSS службы и вспомогательные структуры не спровоцировали аварийный OOM-kill ядром Linux.
Запустите контейнер в фоновом режиме и проверьте системные логи:
# Запуск службы
sudo docker compose up -d
# Проверка статуса доступности (Health Check)
curl -s http://127.0.0.1:7700/health
# Ожидаемый ответ:
# {"status":"available"}
Проверить доступность локального порта и удостовериться, что порт 7700 надежно скрыт от внешнего сканирования, можно через онлайн-сканер открытых портов SysKit.
4. Настройка Nginx Reverse Proxy и защита административных эндпоинтов
Чтобы поисковый движок безопасно принимал запросы с фронтенда вашего сайта по HTTPS на поддомене (например, search.example.com), настроим обратный прокси-сервер Nginx. Мы применим белый список эндпоинтов, настроим CORS-заголовки для браузера и включим Rate Limiting для защиты от DoS-атак.
Конфигурация виртуального хоста Nginx с Rate Limiting и CORS
Создайте файл конфигурации /etc/nginx/sites-available/meilisearch.conf. Обратите внимание: для заголовка Access-Control-Allow-Origin рекомендуется явно указывать домен вашего фронтенда (например, https://example.com), чтобы исключить использование вашего поискового API сторонними ресурсами:
# Ограничение частоты поисковых запросов: 10 запросов в секунду на IP
limit_req_zone $binary_remote_addr zone=search_limit:10m rate=10r/s;
limit_req_status 429;
server {
listen 80;
server_name search.example.com;
client_max_body_size 50M;
# 1. Публичные эндпоинты: поиск по индексам и мульти-поиск
location ~* ^/(indexes/[^/]+/search|multi-search)$ {
# Защита от DoS: 10 r/s с буфером 20 идеально подходят под debounce
limit_req zone=search_limit burst=20 nodelay;
# Поддержка CORS: укажите домен сайта или '*' для публичного API
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Meili-API-Key' always;
# Мгновенный ответ на preflight-запросы браузера
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://127.0.0.1:7700;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Буферизация для быстрой отдачи ответов
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
}
# 2. Мониторинг доступности службы (с базовым rate limit от спама)
location = /health {
limit_req zone=search_limit burst=10 nodelay;
proxy_pass http://127.0.0.1:7700;
proxy_http_version 1.1;
}
# 3. Все остальные пути (/keys, /tasks, /indexes, /dumps): только с localhost
location / {
allow 127.0.0.1;
deny all;
proxy_pass http://127.0.0.1:7700;
proxy_http_version 1.1;
}
}
Директива limit_req zone=search_limit burst=20 nodelay защищает VDS с 1–2 vCPU от DoS-атак ботов при компрометации публичного поискового токена. Скорость 10 запросов в секунду полностью прозрачна для реального пользователя, набирающего текст с задержкой debounce 150 мс, но немедленно отсекает агрессивный парсинг статусом 429 Too Many Requests.
Активируйте сайт и выпустите бесплатный SSL-сертификат Let's Encrypt утилитой Certbot с обязательным флагом --redirect:
# Активация конфигурации и проверка синтаксиса
sudo ln -s /etc/nginx/sites-available/meilisearch.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Выпуск SSL-сертификата с автоматическим редиректом на HTTPS
sudo certbot --nginx --redirect -d search.example.com
Сгенерировать готовые конфигурации веб-сервера под другие проекты можно с помощью нашего генератора Nginx Reverse Proxy.
5. Требования к дисковой подсистеме VDS, оперативной памяти и IOPS
Производительность Meilisearch напрямую зависит от архитектуры дисковой подсистемы хостинга. Движок построен на базе встраиваемого хранилища LMDB, которое взаимодействует с диском через отображение файлов в виртуальную память (mmap).
Как LMDB использует оперативную память и Page Cache
В отличие от реляционных СУБД, выделяющих фиксированный внутренний буфер (например, innodb_buffer_pool_size в MySQL), Meilisearch делегирует кэширование страниц ядру операционной системы:
- Виртуальная память (mmap): Meilisearch отображает файл базы данных в виртуальное адресное пространство процесса. При этом ядро Linux физически выделяет RAM только под те страницы индекса, к которым фактически происходят обращения.
- Системный Page Cache: пока на сервере есть свободная память, ОС удерживает часто запрашиваемые блоки поисковых структур (b-tree) в оперативной памяти. Запросы к прогретому кэшу выполняются с задержками порядка единиц миллисекунд.
- Случайное чтение с диска (Random 4K IOPS): когда оперативной памяти недостаточно для удержания всего индекса, ядро подгружает страницы с диска на лету. На медленных дисках или при превышении лимитов IOPS время ответа может подскакивать до сотен миллисекунд.
При поиске с опечатками и ранжированием по нескольким критериям поисковый движок выполняет серию мелких операций случайного чтения по структуре индекса. Накопители NVMe с высоким запасом случайного чтения обеспечивают плавную выдачу подсказок даже при «холодных» запросах, не попавших в системный Page Cache.
Ориентировочные требования к ресурсам VDS по данным бенчмарков
| Объем базы (документов) | Примерный размер индекса на диске | Рекомендуемые ресурсы VDS | Тип накопителя |
|---|---|---|---|
| До 20 000 (блог, небольшой каталог) | 50–150 МБ | 1 vCPU / 1 ГБ RAM | NVMe SSD (15–20 ГБ) |
| 20 000 – 200 000 (средний интернет-магазин) | 300 МБ – 1.5 ГБ | 2 vCPU / 2 ГБ RAM | NVMe SSD (30–40 ГБ) |
| 200 000 – 1 000 000+ (маркетплейс, база знаний) | 3–8 ГБ | 4 vCPU / 4–8 ГБ RAM | NVMe Enterprise |
Перед развертыванием производственного поиска протестируйте параметры вашей виртуальной машины и сетевой маршрут с помощью нашего анализатора хостинга и провайдеров SysKit. Ниже представлены проверенные облачные серверы с быстрыми накопителями NVMe, оптимальные для работы поисковых баз данных.
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud — Быстрые NVMe VDS
Высокочастотные процессоры 3.3+ ГГц для мгновенного парсинга и ранжирования поисковых запросов, быстрые NVMe-накопители и безлимитный трафик 1 Гбит/с.
Selectel — Надежные облачные серверы
Отказоустойчивая инфраструктура Tier III с гарантированными вычислительными ресурсами и высоким IOPS дисковой подсистемы для баз данных LMDB.
Beget — Простота для веб-разработчиков
Интуитивная панель управления сервером, быстрое развертывание Docker-стека в один клик, автоматические ежедневные бэкапы и отзывчивая поддержка 24/7.
6. Создание индексов, русская морфология, синонимы и ранжирование
После запуска контейнера и настройки Nginx пора перейти к конфигурации самого поискового индекса. Все операции с Meilisearch производятся через лаконичный REST API.
Получение рабочих API-ключей
При первом старте Meilisearch автоматически сгенерировал два базовых ключа: административный (с полным доступом к данным) и поисковый (публичный read-only с действием search). Получим их список с помощью мастер-ключа:
# Запрос списка ключей авторизации (выполняется с сервера)
curl -s -X GET "http://127.0.0.1:7700/keys" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY" | jq .
В выводе найдите объект ключа с описанием Default Search API Key и полем actions: ["search"]. Сохраните значение его поля key — именно оно потребуется для фронтенда.
Создание индекса и тонкая настройка параметров ранжирования
Создадим индекс для каталога товаров products и настроим приоритеты полей через эндпоинт /settings:
# 1. Создание индекса с указанием первичного ключа
curl -s -X POST "http://127.0.0.1:7700/indexes" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY" -H "Content-Type: application/json" --data-binary '{"uid": "products", "primaryKey": "id"}'
# 2. Настройка весов полей, фильтров, взаимных синонимов и лимитов пагинации
curl -s -X PATCH "http://127.0.0.1:7700/indexes/products/settings" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY" -H "Content-Type: application/json" --data-binary '{
"searchableAttributes": [
"title",
"category",
"description",
"sku"
],
"filterableAttributes": [
"category",
"in_stock",
"price"
],
"sortableAttributes": [
"price",
"rating"
],
"synonyms": {
"ноутбук": ["ноут", "лэптоп", "laptop"],
"ноут": ["ноутбук"],
"лэптоп": ["ноутбук"],
"смартфон": ["телефон", "мобильник", "smartphone"],
"телефон": ["смартфон"]
},
"pagination": {
"maxTotalHits": 2000
}
}'
Синонимы: по спецификации Meilisearch синонимы являются однонаправленными. Запись "ноутбук": ["ноут"] означает, что запрос «ноутбук» найдет «ноут», но не наоборот. Чтобы поиск работал симметрично, объявляйте взаимные связи явно.
Пагинация: по умолчанию Meilisearch ограничивает максимальную глубину выдачи 1000 результатами (maxTotalHits: 1000) для защиты сервера от парсеров. Если вашему каталогу требуется листать глубже, увеличьте параметр в секции pagination.
Импорт пакета документов
Загрузка данных производится отправкой массива JSON. Meilisearch обрабатывает документы пакетами в асинхронной очереди задач:
# Добавление документов в индекс
curl -s -X POST "http://127.0.0.1:7700/indexes/products/documents" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY" -H "Content-Type: application/json" --data-binary '[
{
"id": 1,
"title": "Ультрабук ThinkPad X1 Carbon Gen 11",
"category": "ноутбук",
"price": 149000,
"in_stock": true,
"description": "Легкий бизнес-ноутбук с процессором Intel Core i7 и ярким экраном"
},
{
"id": 2,
"title": "Беспроводные наушники Sony WH-1000XM5",
"category": "наушники",
"price": 32900,
"in_stock": true,
"description": "Полноразмерная гарнитура с активным шумоподавлением"
}
]'
В ответ сервер вернет JSON с taskUid. Проверить статус индексации можно локальным запросом к http://127.0.0.1:7700/tasks/{taskUid}.
7. Интеграция с сайтом: поиск на лету через REST API и JavaScript
Meilisearch поддерживает два метода отправки поисковых запросов:
POST /indexes/{uid}/search: основной метод для веб-приложений. Позволяет передавать сложное тело запроса (массивы фильтров, сортировку, параметры пагинации и подсветки).GET /indexes/{uid}/search?q=запрос: легковесный эндпоинт, удобный для прямых ссылок, адресной строки браузера и простых поисковых форм.
Безопасный фронтенд-скрипт с защитой от XSS и Debounce
При отображении результатов поиска в браузере критически важно помнить о безопасности: Meilisearch возвращает в поле _formatted оригинальный текст без экранирования HTML-тегов. Если в названии или описании товара встретится вредоносная разметка (например, внедренная злоумышленником через комментарии или форму отзывов), прямая вставка через innerHTML приведет к уязвимости DOM XSS (Cross-Site Scripting).
Чтобы исключить эту угрозу, мы используем строгие текстовые разделители (###HL_START### / ###HL_END###), предварительно экранируем весь текст через безопасный узел DOM (textContent), и только затем заменяем маркеры на стилизованные теги <mark>:
<!-- Поле ввода и контейнер поисковой выдачи -->
<div class="search-box">
<input type="text" id="live-search-input" placeholder="Поиск товаров (например, ноут)..." />
<div id="search-results-container"></div>
</div>
<script>
const MEILI_HOST = 'https://search.example.com';
const MEILI_SEARCH_KEY = 'ВАШ_ПУБЛИЧНЫЙ_SEARCH_API_KEY'; // Ключ с правами strictly actions: ["search"]
const INDEX_NAME = 'products';
let debounceTimeout = null;
const inputElem = document.getElementById('live-search-input');
const resultsElem = document.getElementById('search-results-container');
// Безопасное экранирование HTML для предотвращения DOM XSS
function safeHighlight(rawFormattedText) {
if (!rawFormattedText) return '';
const tempDiv = document.createElement('div');
tempDiv.textContent = rawFormattedText; // Надежно экранирует любые опасные теги
const escaped = tempDiv.innerHTML;
return escaped
.replaceAll('###HL_START###', '<mark style="background: #38bdf8; color: #0f172a; border-radius: 2px; padding: 0 2px;">')
.replaceAll('###HL_END###', '</mark>');
}
inputElem.addEventListener('input', (e) => {
clearTimeout(debounceTimeout);
const query = e.target.value.trim();
if (!query) {
resultsElem.innerHTML = '';
return;
}
// Задержка 150 мс для предотвращения лишних запросов при быстром наборе
debounceTimeout = setTimeout(async () => {
try {
const response = await fetch(`${MEILI_HOST}/indexes/${INDEX_NAME}/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${MEILI_SEARCH_KEY}`
},
body: JSON.stringify({
q: query,
limit: 8,
attributesToHighlight: ['title'],
highlightPreTag: '###HL_START###',
highlightPostTag: '###HL_END###'
})
});
if (!response.ok) throw new Error(`Ошибка HTTP: ${response.status}`);
const data = await response.json();
renderSearchResults(data.hits);
} catch (err) {
console.error('Search error:', err);
}
}, 150);
});
function renderSearchResults(hits) {
if (!hits.length) {
resultsElem.innerHTML = '<div style="padding: 10px; color: #94a3b8;">Ничего не найдено</div>';
return;
}
resultsElem.innerHTML = hits.map(hit => {
const highlightedTitle = safeHighlight(hit._formatted?.title || hit.title);
const safePrice = Number(hit.price) || 0;
return `
<div style="padding: 12px; border-bottom: 1px solid rgba(255,255,255,0.08);">
<div style="font-weight: 600; color: #f8fafc;">${highlightedTitle}</div>
<div style="font-size: 0.85rem; color: #38bdf8;">${safePrice} ₽</div>
</div>
`;
}).join('');
}
</script>
Если передавать стандартные теги <mark> прямо из Meilisearch в innerHTML, злоумышленник сможет загрузить документ с названием <img src=x onerror=alert(document.cookie)>, и этот код выполнится в браузере пользователя. Двухэтапная обработка через div.textContent и замену кастомных маркеров полностью исключает исполнение произвольного кода.
Такой подход обеспечивает мгновенную реакцию интерфейса со скоростью сетевого ответа 20–50 мс на типовых интернет-каналах. При необходимости управления веб-инструментами и утилитами разработчика на сервере ознакомьтесь также с обзором развертывания IT-Tools в Docker Compose.
8. Снапшоты, дампы базы и автоматические бэкапы данных
При эксплуатации поискового сервера важно понимать разницу между двумя встроенными механизмами резервного копирования Meilisearch: снапшотами (Snapshots) и дампами (Dumps).
Снапшоты vs Дампы: что и когда применять
| Характеристика | Снапшот (Snapshot) | Дамп (Dump) |
|---|---|---|
| Формат данных | Снимок базы со всеми структурами индексов и задачами | Сжатый платформенно-независимый архив .dump (схема, документы, настройки) |
| Скорость создания и восстановления | Быстрое копирование файлов (без перестроения индексов) | Требует последовательной выгрузки и полной переиндексации при импорте |
| Совместимость версий | Только для той же мажорной/минорной версии | Кросс-версионная (для миграций и обновлений Meilisearch) |
| Сценарий использования | Ежедневные бэкапы на случай сбоя диска или сервера | Перед обновлением Docker-образа на новую ветку |
Создание снапшота и дампа через консоль
# Создание снапшота через локальный API
curl -s -X POST "http://127.0.0.1:7700/snapshots" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY"
# Создание универсального дампа перед обновлением
curl -s -X POST "http://127.0.0.1:7700/dumps" -H "Authorization: Bearer ВАШ_MEILI_MASTER_KEY"
Чтобы автоматизировать процесс создания снапшотов, добавьте параметр --schedule-snapshot 86400 в запуск контейнера или запускайте cron-задачу раз в сутки.
9. Чек-лист сисадмина: эксплуатация Meilisearch в production
Перед переводом поискового сервиса в промышленную эксплуатацию сверьтесь со списком критических настроек:
-
✔ Фиксация версии Docker: указана стабильная ветка (например,
v1.54Community Edition) вместо плавающего тега:latestили:v1для защиты базы LMDB. -
✔ Изоляция сетевого порта: порт 7700 контейнера привязан строго к
127.0.0.1, минуя автоправила iptables в обход UFW. -
✔ Криптостойкий Master Key: длина ключа превышает 16 байт (рекомендуется 32 байта Base64), а режим
MEILI_ENV=productionвключен. -
✔ Защита от OOM Killer: значение
MEILI_MAX_INDEXING_MEMORY(500MB) согласовано с лимитомmem_limit(768m) с запасом от 250 МБ. - ✔ Защита эндпоинтов Nginx: публичный доступ открыт только к поиску, все служебные маршруты закрыты политикой белого списка (403 Forbidden).
-
✔ Rate Limiting в Nginx: настроена зона
limit_reqдля предотвращения DoS-атак на компактный VDS при компрометации публичного ключа. -
✔ Точный CORS Origin: заголовок
Access-Control-Allow-Originпривязан к домену сайта, предотвращая использование поискового сервиса сторонними проектами. -
✔ Фронтенд-безопасность: в клиентский JS передан только
Default Search API Key(read-only), а вывод экранирован функциейsafeHighlight()против DOM XSS. - ✔ Диски NVMe и своп: VDS работает на быстром NVMe с достаточным запасом IOPS, а файл подкачки swap защищает от сбоев при индексации крупных дампов.
- ✔ Регулярные снапшоты: настроено автоматическое резервное копирование снимков базы на внешний сервер или S3-хранилище.
Для удобного управления контейнерами через визуальный интерфейс обратите внимание на стек менеджера Dockge для Docker Compose, а тонко настроить ограничения ресурсов контейнеров поможет наш генератор лимитов Docker Compose.