Self-Hosting Guide: Deploy Meilisearch for Fast Search
·OSSAlt Team
meilisearchsearchself-hostingdockerguide
Self-Hosting Guide: Deploy Meilisearch for Fast Search
Meilisearch is the open source Algolia alternative — typo-tolerant, instant search that returns results in under 50ms. Self-hosting removes the 10K document limit on the free tier and eliminates per-search pricing.
Requirements
- VPS with 1 GB RAM minimum (scale with index size)
- Docker
- Domain name (e.g.,
search.yourdomain.com) - 10+ GB disk (depends on data volume)
Step 1: Deploy with Docker
docker run -d \
--name meilisearch \
--restart unless-stopped \
-p 7700:7700 \
-v meili_data:/meili_data \
-e MEILI_MASTER_KEY=your-master-key-min-16-chars \
-e MEILI_ENV=production \
getmeili/meilisearch:latest
Generate a master key:
openssl rand -hex 24
Step 2: Reverse Proxy (Caddy)
# /etc/caddy/Caddyfile
search.yourdomain.com {
reverse_proxy localhost:7700
}
sudo systemctl restart caddy
Step 3: Get API Keys
Meilisearch auto-generates API keys from the master key:
# List API keys
curl -s https://search.yourdomain.com/keys \
-H 'Authorization: Bearer your-master-key' | jq
| Key | Purpose | Use In |
|---|---|---|
| Default Search API Key | Search only (read) | Frontend |
| Default Admin API Key | Full access (read/write) | Backend |
| Master Key | Manages all keys | Never expose |
Step 4: Create an Index and Add Documents
# Create index with primary key
curl -X POST 'https://search.yourdomain.com/indexes/products/documents' \
-H 'Authorization: Bearer your-admin-api-key' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 1,
"title": "Wireless Headphones",
"description": "Noise-cancelling Bluetooth headphones",
"category": "Electronics",
"price": 79.99
},
{
"id": 2,
"title": "Mechanical Keyboard",
"description": "Cherry MX Blue switches, RGB backlight",
"category": "Electronics",
"price": 129.99
}
]'
Step 5: Configure Index Settings
# Set searchable attributes (order = priority)
curl -X PUT 'https://search.yourdomain.com/indexes/products/settings' \
-H 'Authorization: Bearer your-admin-api-key' \
-H 'Content-Type: application/json' \
--data-binary '{
"searchableAttributes": ["title", "description", "category"],
"filterableAttributes": ["category", "price"],
"sortableAttributes": ["price"],
"displayedAttributes": ["title", "description", "category", "price"],
"typoTolerance": {
"enabled": true,
"minWordSizeForTypos": { "oneTypo": 4, "twoTypos": 8 }
},
"pagination": { "maxTotalHits": 1000 }
}'
Step 6: Search
# Basic search
curl 'https://search.yourdomain.com/indexes/products/search' \
-H 'Authorization: Bearer your-search-api-key' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "headphons" }'
# Returns "Wireless Headphones" despite typo ✨
# Search with filters
curl 'https://search.yourdomain.com/indexes/products/search' \
-H 'Authorization: Bearer your-search-api-key' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "keyboard",
"filter": "price < 150",
"sort": ["price:asc"]
}'
Step 7: Frontend Integration
JavaScript SDK:
npm install meilisearch
import { MeiliSearch } from 'meilisearch'
const client = new MeiliSearch({
host: 'https://search.yourdomain.com',
apiKey: 'your-search-api-key', // Search key only!
})
const results = await client.index('products').search('headphones', {
limit: 10,
filter: ['category = Electronics'],
})
InstantSearch (Algolia-compatible UI):
npm install react-instantsearch @meilisearch/instant-meilisearch
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch'
import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'
const { searchClient } = instantMeiliSearch(
'https://search.yourdomain.com',
'your-search-api-key'
)
function SearchPage() {
return (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<Hits hitComponent={Hit} />
</InstantSearch>
)
}
function Hit({ hit }) {
return (
<div>
<h3>{hit.title}</h3>
<p>{hit.description}</p>
<span>${hit.price}</span>
</div>
)
}
Step 8: Keep Data in Sync
Option 1: Batch sync (cron)
# Export from your database and push to Meilisearch daily
*/15 * * * * /usr/local/bin/sync-search.sh
Option 2: Real-time sync (webhook/event)
// After creating/updating a product in your app
await meiliClient.index('products').updateDocuments([updatedProduct])
// After deleting
await meiliClient.index('products').deleteDocument(productId)
Option 3: Database trigger Use n8n or a custom webhook to sync on database changes.
Production Hardening
Docker Compose (recommended for production):
services:
meilisearch:
image: getmeili/meilisearch:latest
container_name: meilisearch
restart: unless-stopped
ports:
- "7700:7700"
volumes:
- meili_data:/meili_data
environment:
- MEILI_MASTER_KEY=your-master-key
- MEILI_ENV=production
- MEILI_MAX_INDEXING_MEMORY=1024Mb
- MEILI_MAX_INDEXING_THREADS=2
volumes:
meili_data:
Backups:
# Snapshot (built-in)
curl -X POST 'https://search.yourdomain.com/snapshots' \
-H 'Authorization: Bearer your-master-key'
# Or backup the data volume
docker run --rm -v meili_data:/data -v /backups:/backup alpine \
tar czf /backup/meili-$(date +%Y%m%d).tar.gz /data
Updates:
docker pull getmeili/meilisearch:latest
docker stop meilisearch && docker rm meilisearch
# Re-run the docker run command (data persists in volume)
Monitoring:
- Health check:
GET /health(returns{ "status": "available" }) - Stats:
GET /stats(index sizes, document counts) - Monitor search latency (should be < 50ms)
Resource Usage
| Documents | RAM | CPU | Disk |
|---|---|---|---|
| 1-100K | 512 MB | 1 core | 5 GB |
| 100K-1M | 2 GB | 2 cores | 20 GB |
| 1M-10M | 8 GB | 4 cores | 50 GB |
Rule of thumb: Meilisearch needs ~2x your dataset size in RAM for optimal performance.
VPS Recommendations
| Provider | Spec (500K docs) | Price |
|---|---|---|
| Hetzner | 2 vCPU, 4 GB RAM | €4.50/month |
| DigitalOcean | 2 vCPU, 4 GB RAM | $24/month |
| Linode | 2 vCPU, 4 GB RAM | $24/month |
Compare search engines on OSSAlt — speed, features, and self-hosting options side by side.