Development beginner ⏱ 30 minutes

Meilisearch: Fast Search Engine

Typo-tolerant search engine. Easy to use with instant search results.

By Homelab Team • 2025-01-15
#meilisearch#search#api#fast#docker

Introduction

Meilisearch is a powerful, open-source search engine that delivers instant search results with an out-of-the-box user experience. Built with Rust for blazing-fast performance, Meilisearch provides typo tolerance, filters, faceting, and synonyms support. It’s designed to be simple to deploy and integrate, making it perfect for adding search functionality to applications, documentation sites, e-commerce platforms, or content management systems.

By the end of this guide, you’ll have a production-ready Meilisearch installation that:

  • Delivers sub-millisecond search results
  • Handles typos and fuzzy matching automatically
  • Provides a powerful REST API for integration
  • Includes multi-index support for different data types
  • Offers filtering, sorting, and faceting capabilities
  • Features comprehensive security and authentication

Prerequisites

Before starting, ensure you have:

  • A Linux server with at least 2GB RAM (4GB recommended for production)
  • Docker and Docker Compose installed
  • Basic understanding of REST APIs and JSON
  • At least 10GB free disk space for indexes
  • Root or sudo access
  • Optional: Domain name for public access

What Makes Meilisearch Special?

Key Features

Instant Search: Results appear as users type, with response times under 50ms for most queries.

Typo Tolerance: Automatically handles misspellings and fuzzy matching without configuration.

Easy Integration: RESTful API with official SDKs for JavaScript, Python, Ruby, PHP, Go, Rust, and more.

Relevant Results: Built-in ranking rules that deliver the most relevant results first.

Faceted Search: Filter results by categories, tags, prices, dates, or any custom attributes.

Highlighting: Automatically highlights matching terms in search results.

Language Support: Works with any language, with special optimizations for Latin-based languages.

Use Cases

  • Application Search: Add instant search to your web or mobile applications
  • Documentation Sites: Make documentation searchable and discoverable
  • E-commerce: Product search with filters, sorting, and faceting
  • Content Management: Search articles, blog posts, and media
  • Site Search: Replace slow SQL queries with fast full-text search

Architecture Overview

Our setup consists of:

  1. Meilisearch Server: Main search engine with HTTP API
  2. Persistent Volume: Stores indexes and data
  3. API Keys: Master key and search-only keys for security
  4. Optional Reverse Proxy: Nginx or Traefik for HTTPS access

The search flow:

  • Documents are indexed via the REST API
  • Meilisearch builds searchable indexes in memory
  • Search queries hit the /search endpoint
  • Results are returned with highlighting and relevance ranking
  • Filters and facets can be applied in real-time

Directory Structure Setup

Create the necessary directory structure:

mkdir -p ~/meilisearch/{data,config,scripts}
cd ~/meilisearch

Docker Compose Configuration

Create a docker-compose.yml file:

version: '3.8'

services:
  meilisearch:
    container_name: meilisearch
    image: getmeili/meilisearch:v1.6
    restart: unless-stopped
    ports:
      - "7700:7700"
    environment:
      # CRITICAL: Change this to a strong random string
      - MEILI_MASTER_KEY=YOUR_SUPER_SECRET_MASTER_KEY_MINIMUM_16_CHARS
      - MEILI_ENV=production
      - MEILI_HTTP_ADDR=0.0.0.0:7700
      - MEILI_DB_PATH=/meili_data
      - MEILI_LOG_LEVEL=INFO
      # Optional: Enable analytics (set to false for privacy)
      - MEILI_NO_ANALYTICS=true
      # Optional: Set max payload size (in bytes)
      - MEILI_HTTP_PAYLOAD_SIZE_LIMIT=104857600
    volumes:
      - ./data:/meili_data
    networks:
      - meilisearch_net
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--spider", "http://localhost:7700/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s

networks:
  meilisearch_net:
    driver: bridge

Important Notes:

  • Replace YOUR_SUPER_SECRET_MASTER_KEY_MINIMUM_16_CHARS with a strong random key
  • Generate a secure key with: openssl rand -base64 32
  • The master key is required in production mode and must be at least 16 characters
  • Keep your master key secret and never commit it to version control

Generating a Secure Master Key

Generate a strong master key:

# Generate a 32-character random key
openssl rand -base64 32

# Or use this alternative
head -c 32 /dev/urandom | base64

Update your docker-compose.yml with the generated key.

Starting Meilisearch

Start the container:

docker compose up -d

Verify it’s running:

docker compose ps
docker compose logs -f meilisearch

Check the health endpoint:

curl http://localhost:7700/health

You should see: {"status":"available"}

API Authentication

Meilisearch uses API keys for authentication. There are two types:

1. Master Key

The master key has full access to all operations. Use it for:

  • Creating and managing indexes
  • Adding and updating documents
  • Managing API keys
  • Administrative tasks

2. API Keys

Generate search-only and admin API keys:

# Get the default admin API key (for adding documents)
curl   -X GET 'http://localhost:7700/keys'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

# Create a custom search-only API key
curl   -X POST 'http://localhost:7700/keys'   -H 'Authorization: Bearer YOUR_MASTER_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "description": "Search-only key for frontend",
    "actions": ["search"],
    "indexes": ["*"],
    "expiresAt": null
  }'

Save the generated keys securely. Use the search-only key in your frontend applications.

Creating Your First Index

Create an index for a blog or product catalog:

# Create an index
curl   -X POST 'http://localhost:7700/indexes'   -H 'Authorization: Bearer YOUR_MASTER_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "uid": "articles",
    "primaryKey": "id"
  }'

Configuring Index Settings

Configure searchable attributes, ranking rules, and filters:

# Update index settings
curl   -X PATCH 'http://localhost:7700/indexes/articles/settings'   -H 'Authorization: Bearer YOUR_MASTER_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "searchableAttributes": [
      "title",
      "content",
      "tags"
    ],
    "filterableAttributes": [
      "category",
      "author",
      "published_date",
      "tags"
    ],
    "sortableAttributes": [
      "published_date",
      "title"
    ],
    "rankingRules": [
      "words",
      "typo",
      "proximity",
      "attribute",
      "sort",
      "exactness"
    ],
    "stopWords": [
      "the",
      "a",
      "an"
    ],
    "synonyms": {
      "docker": ["container", "containerization"],
      "kubernetes": ["k8s", "k3s"]
    },
    "distinctAttribute": null,
    "typoTolerance": {
      "enabled": true,
      "minWordSizeForTypos": {
        "oneTypo": 5,
        "twoTypos": 9
      }
    }
  }'

Adding Documents

Add documents to your index:

# Add documents in bulk
curl   -X POST 'http://localhost:7700/indexes/articles/documents'   -H 'Authorization: Bearer YOUR_MASTER_KEY'   -H 'Content-Type: application/json'   --data-binary '[
    {
      "id": 1,
      "title": "Getting Started with Docker",
      "content": "Docker is a platform for developing, shipping, and running applications in containers...",
      "category": "DevOps",
      "author": "Homelab Team",
      "tags": ["docker", "containers", "devops"],
      "published_date": 1705276800
    },
    {
      "id": 2,
      "title": "Kubernetes vs Docker Swarm",
      "content": "Comparing container orchestration platforms...",
      "category": "DevOps",
      "author": "Homelab Team",
      "tags": ["kubernetes", "docker", "orchestration"],
      "published_date": 1705363200
    }
  ]'

Performing Searches

Search your indexed documents:

# Basic search
curl   -X POST 'http://localhost:7700/indexes/articles/search'   -H 'Authorization: Bearer YOUR_SEARCH_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "q": "docker container"
  }'

# Advanced search with filters and sorting
curl   -X POST 'http://localhost:7700/indexes/articles/search'   -H 'Authorization: Bearer YOUR_SEARCH_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "q": "docker",
    "filter": "category = DevOps AND published_date > 1705276800",
    "sort": ["published_date:desc"],
    "attributesToHighlight": ["title", "content"],
    "highlightPreTag": "<mark>",
    "highlightPostTag": "</mark>",
    "limit": 20,
    "offset": 0
  }'

# Faceted search
curl   -X POST 'http://localhost:7700/indexes/articles/search'   -H 'Authorization: Bearer YOUR_SEARCH_KEY'   -H 'Content-Type: application/json'   --data-binary '{
    "q": "homelab",
    "facets": ["category", "author", "tags"]
  }'

JavaScript Integration Example

Here’s how to integrate Meilisearch in a web application:

// Install: npm install meilisearch
import { MeiliSearch } from 'meilisearch';

const client = new MeiliSearch({
  host: 'http://localhost:7700',
  apiKey: 'YOUR_SEARCH_KEY'
});

// Perform a search
async function searchArticles(query) {
  const index = client.index('articles');

  const results = await index.search(query, {
    filter: 'category = DevOps',
    attributesToHighlight: ['title', 'content'],
    limit: 10
  });

  return results;
}

// Add documents
async function addArticle(article) {
  const index = client.index('articles');
  await index.addDocuments([article]);
}

// Update documents
async function updateArticle(id, updates) {
  const index = client.index('articles');
  await index.updateDocuments([{ id, ...updates }]);
}

// Delete documents
async function deleteArticle(id) {
  const index = client.index('articles');
  await index.deleteDocument(id);
}

Python Integration Example

Using Meilisearch with Python:

# Install: pip install meilisearch
import meilisearch

client = meilisearch.Client('http://localhost:7700', 'YOUR_SEARCH_KEY')

# Get index
index = client.index('articles')

# Search
results = index.search('docker', {
    'filter': 'category = DevOps',
    'attributesToHighlight': ['title', 'content'],
    'limit': 10
})

# Add documents
articles = [
    {
        'id': 3,
        'title': 'Homelab Setup Guide',
        'content': 'Building your first homelab...',
        'category': 'Homelab',
        'author': 'Homelab Team',
        'tags': ['homelab', 'hardware'],
        'published_date': 1705449600
    }
]

index.add_documents(articles)

# Update documents
index.update_documents([
    {'id': 3, 'title': 'Updated Homelab Setup Guide'}
])

# Delete documents
index.delete_document(3)

Instant Search UI

Create a simple instant search interface:

<!DOCTYPE html>
<html>
<head>
    <title>Instant Search</title>
    <script src="https://cdn.jsdelivr.net/npm/meilisearch@latest/dist/bundles/meilisearch.umd.js"></script>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; }
        #search-box { width: 100%; padding: 12px; font-size: 16px; border: 2px solid #ddd; }
        .result { padding: 15px; border-bottom: 1px solid #eee; }
        .result h3 { margin: 0 0 10px 0; }
        mark { background-color: yellow; font-weight: bold; }
    </style>
</head>
<body>
    <h1>Search Articles</h1>
    <input type="text" id="search-box" placeholder="Search...">
    <div id="results"></div>

    <script>
        const client = new MeiliSearch({
            host: 'http://localhost:7700',
            apiKey: 'YOUR_SEARCH_KEY'
        });

        const searchBox = document.getElementById('search-box');
        const resultsDiv = document.getElementById('results');

        searchBox.addEventListener('input', async (e) => {
            const query = e.target.value;

            if (query.length < 2) {
                resultsDiv.innerHTML = '';
                return;
            }

            const index = client.index('articles');
            const results = await index.search(query, {
                attributesToHighlight: ['title', 'content'],
                highlightPreTag: '<mark>',
                highlightPostTag: '</mark>',
                limit: 10
            });

            displayResults(results.hits);
        });

        function displayResults(hits) {
            if (hits.length === 0) {
                resultsDiv.innerHTML = '<p>No results found</p>';
                return;
            }

            resultsDiv.innerHTML = hits.map(hit => `
                <div class="result">
                    <h3>${hit._formatted.title}</h3>
                    <p>${hit._formatted.content.substring(0, 200)}...</p>
                    <small>Category: ${hit.category} | Author: ${hit.author}</small>
                </div>
            `).join('');
        }
    </script>
</body>
</html>

Backup and Restore

Create a backup script:

#!/bin/bash
# ~/meilisearch/scripts/backup.sh

BACKUP_DIR=~/meilisearch/backups
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/meilisearch_backup_$TIMESTAMP.tar.gz"

echo "Creating backup..."

# Stop Meilisearch
cd ~/meilisearch
docker compose stop meilisearch

# Create backup
tar -czf "$BACKUP_FILE" data/

# Start Meilisearch
docker compose start meilisearch

# Keep only last 7 backups
ls -t "$BACKUP_DIR"/meilisearch_backup_*.tar.gz | tail -n +8 | xargs -r rm

echo "Backup completed: $BACKUP_FILE"

Make it executable and schedule:

chmod +x ~/meilisearch/scripts/backup.sh

# Add to crontab for daily backups at 2 AM
crontab -e
# Add: 0 2 * * * /home/YOUR_USER/meilisearch/scripts/backup.sh

Monitoring and Stats

Get index statistics:

# Get index stats
curl   -X GET 'http://localhost:7700/indexes/articles/stats'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

# Get all indexes
curl   -X GET 'http://localhost:7700/indexes'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

# Get system stats
curl   -X GET 'http://localhost:7700/stats'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

# Check version
curl   -X GET 'http://localhost:7700/version'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

Reverse Proxy with Nginx

Secure Meilisearch with HTTPS:

server {
    listen 80;
    server_name search.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name search.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/search.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/search.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:7700;
        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;

        # CORS headers for search API
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'Authorization, Content-Type';

        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
}

Performance Tuning

Optimize for production:

# docker-compose.yml additions
services:
  meilisearch:
    # ... existing config ...
    environment:
      # Increase max indexing threads
      - MEILI_MAX_INDEXING_THREADS=4
      # Increase max indexing memory (in bytes, 2GB)
      - MEILI_MAX_INDEXING_MEMORY=2147483648
      # Set max task database size (in bytes, 1GB)
      - MEILI_MAX_TASK_DB_SIZE=1073741824
      # Enable gzip compression
      - MEILI_EXPERIMENTAL_ENABLE_METRICS=true
    deploy:
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

Troubleshooting

High Memory Usage

# Check memory usage
docker stats meilisearch

# Reduce max indexing memory
# Update docker-compose.yml:
- MEILI_MAX_INDEXING_MEMORY=1073741824  # 1GB

Slow Indexing

# Increase indexing threads
- MEILI_MAX_INDEXING_THREADS=8

# Check task queue
curl -X GET 'http://localhost:7700/tasks'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

Search Not Returning Results

# Check if documents are indexed
curl -X GET 'http://localhost:7700/indexes/articles/stats'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

# Verify searchable attributes
curl -X GET 'http://localhost:7700/indexes/articles/settings/searchable-attributes'   -H 'Authorization: Bearer YOUR_MASTER_KEY'

CORS Issues

Add CORS headers in reverse proxy or use Meilisearch’s built-in CORS:

environment:
  - MEILI_HTTP_CORS_ALLOW_ORIGIN=*

Container Won’t Start

# Check logs
docker compose logs meilisearch

# Verify master key length
echo -n "YOUR_MASTER_KEY" | wc -c
# Must be at least 16 characters

# Check port conflicts
sudo netstat -tulpn | grep 7700

Security Best Practices

  1. Use Strong Master Keys: Generate cryptographically secure random keys
  2. Limit API Key Permissions: Create separate keys for search and admin operations
  3. Use HTTPS: Always use SSL/TLS in production
  4. Restrict Network Access: Use firewall rules to limit access
  5. Regular Updates: Keep Meilisearch updated to the latest version
  6. Monitor Logs: Enable logging and monitor for suspicious activity
  7. Backup Regularly: Automate backups of your data directory

Conclusion

You now have a production-ready Meilisearch installation providing lightning-fast search capabilities. Meilisearch’s instant search, typo tolerance, and easy integration make it perfect for adding search to any application.

For more advanced features, explore:

  • Multi-tenancy with index isolation
  • Geo-search capabilities
  • Custom ranking formulas
  • Search analytics and monitoring
  • Meilisearch Cloud for managed hosting

Happy searching!