← Blog

Self-host Meilisearch for fast search

Railyard

August 10, 2026 · 6 min read

Follow this on your own server. Start free, no card needed

Algolia and similar hosted search APIs bill by search volume and by record count, which adds up fast once a site has real traffic. Meilisearch is an open-source search engine written in Rust that indexes typo-tolerant, instant search over your own data, and it’s light enough to run on the cheapest server a cloud provider sells. By the end of this guide you’ll have it running behind HTTPS with a real master key protecting it, a snapshot you can restore from, and a short upgrade path.

What you’ll need

  • A server with Docker and the Compose plugin. A Hetzner CX23 (2 vCPU, 4 GB RAM, 40 GB NVMe, €5.99/month excl. VAT) comfortably runs Meilisearch for a small to medium index.
  • A domain or subdomain pointed at the server’s IP address.
  • SSH access to the server.
  • Your data ready to index: Meilisearch holds documents you push to it, it doesn’t crawl anything on its own.

Step 1: Generate a real master key

Meilisearch refuses to start in production without one. MEILI_ENV=production requires a key of at least 16 bytes, and without it (or with a shorter one) the server logs a suggested key and exits rather than running unprotected:

openssl rand -base64 48

Put the result in a .env file next to your compose file, not committed to version control:

MEILI_MASTER_KEY=paste-the-generated-value-here
chmod 600 .env

Step 2: Write the Docker Compose file

services:
  meilisearch:
    image: getmeili/meilisearch:v1.55.0
    restart: unless-stopped
    environment:
      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
      MEILI_ENV: production
      MEILI_NO_ANALYTICS: "true"
    volumes:
      - ./meili_data:/meili_data
    expose:
      - "7700"

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - meilisearch

volumes:
  caddy_data:
  caddy_config:

This guide pins v1.55.0, the current stable release as of writing. MEILI_ENV=production does two things: it disables the built-in search playground UI at the root path (a convenience for local development that has no business being reachable on a public server), and it’s what makes the master key mandatory rather than optional. MEILI_NO_ANALYTICS=true opts the instance out of Meilisearch’s own anonymous usage reporting. ./meili_data is a plain bind mount rather than a named volume specifically so a backup in Step 5 is just copying a folder, and it’s where every index and task log actually lives.

Check it parses:

docker compose config

Step 3: Point DNS and add Caddy

Create an A record for your subdomain pointing at the server’s IP, then add a Caddyfile next to compose.yml:

search.yourdomain.com {
	reverse_proxy meilisearch:7700
}

Step 4: Start the stack and create your first index

docker compose up -d
docker compose ps
curl https://search.yourdomain.com/health

You should see both services Up and the health check return {"status":"available"}. Create an index and add a document to confirm the master key actually works:

curl -X POST 'https://search.yourdomain.com/indexes' \
  -H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
  -H 'Content-Type: application/json' \
  --data-binary '{ "uid": "movies", "primaryKey": "id" }'

curl -X POST 'https://search.yourdomain.com/indexes/movies/documents' \
  -H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
  -H 'Content-Type: application/json' \
  --data-binary '[{ "id": 1, "title": "Carol" }]'

You should get a taskUid back from both calls. Check the task finished, and that a search actually finds the document:

curl "https://search.yourdomain.com/indexes/movies/search?q=carol" \
  -H "Authorization: Bearer ${MEILI_MASTER_KEY}"

You should see "title": "Carol" in the hits array. From here, your application should mint a scoped, non-master API key per use case (one that can only search, not manage indexes) through the /keys endpoint, rather than shipping the master key itself to a browser.

Step 5: Back up /meili_data

Meilisearch’s own /snapshots endpoint (actually a scheduled dump, not a live snapshot API call) writes a .snapshot file inside the container on an interval you configure, but the simplest and most portable backup is a tarball of the volume itself while the container isn’t actively writing:

cat > /opt/meilisearch/backup.sh <<'EOF'
#!/bin/sh
cd /opt/meilisearch
docker compose stop meilisearch
tar czf backups/meili-$(date +%F).tar.gz meili_data
docker compose start meilisearch
find backups -name '*.tar.gz' -mtime +14 -delete
EOF
mkdir -p /opt/meilisearch/backups
chmod +x /opt/meilisearch/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/meilisearch/backup.sh") | crontab -

Run it once by hand and check it worked: ./backup.sh && ls -lh backups. You should see a .tar.gz file with today’s date. The brief stop and start keeps the archive internally consistent; for a public-facing search index, a few seconds of unavailability at 3am is a reasonable trade for a backup you can actually trust. Copy the file off the server too, so losing the box doesn’t also mean losing the index.

Step 6: Upgrade

docker compose pull meilisearch
docker compose up -d meilisearch

Meilisearch migrates its own on-disk data format automatically on startup when it can, but the project’s own release notes call out version jumps that require re-indexing through a dump-and-import rather than an in-place upgrade; read the changelog for the version you’re moving to before a multi-version jump, particularly across a major version boundary.

The shortcut: do it with Railyard

Railyard ships Meilisearch as a one-click application template (meilisearch in the templates catalog), built from the official getmeili/meilisearch image with MEILI_NO_ANALYTICS already set and a persistent data volume wired up. Deploy it directly: app.railyard.run/deploy?template=meilisearch. One real difference from the manual path above: the template currently tracks the floating latest tag rather than a pinned version, so where this guide has you choose and upgrade a specific release yourself, Railyard moves with upstream automatically; set MEILI_MASTER_KEY yourself as a variable on the app’s Settings page either way, it isn’t generated for you. See deploy with a button for how one-click templates work in general, and databases and backups for how persistent volumes are handled on the platform.

Troubleshooting

The container exits immediately with a message about the master key. MEILI_ENV=production without MEILI_MASTER_KEY set, or with one under 16 bytes, makes Meilisearch refuse to start rather than run open. Re-check the .env file from Step 1 actually has a real value and that docker compose config shows it resolved, not a blank string.

401 Unauthorized on every API call. The Authorization: Bearer <key> header is missing, or you’re passing the wrong key, remember the master key can do everything, including minting other keys, but any request still needs it (or a scoped key) in the header; there’s no unauthenticated path once a master key is set.

HTTPS not issuing. Check docker compose logs caddy for the ACME error; it’s almost always the DNS record from Step 3 not yet pointing at this server, confirm with dig +short search.yourdomain.com.

Search results don’t include a document you just added. Indexing is asynchronous; every write returns a taskUid, and the document isn’t searchable until that task finishes. Poll /tasks/{taskUid} if you need to confirm completion programmatically rather than guessing with a fixed delay.

Out of memory on a small server with a large index. Meilisearch keeps a meaningful amount of index data in memory for query speed; a CX23’s 4 GB is fine for a modest catalog, but a multi-million document index wants the CX33 (8 GB) or larger instead of swap, since search latency (not just stability) suffers once it’s swapping.

FAQ

Is Meilisearch a replacement for Elasticsearch? For most application search use cases (an e-commerce catalog, a docs site, in-app search), yes, and with far less operational overhead; Elasticsearch still wins for log aggregation and complex analytics workloads Meilisearch was never built for.

Can multiple apps share one Meilisearch instance? Yes, indexes are namespaced by uid within the same instance, and scoped API keys can restrict a given key to specific indexes, so one instance safely serves more than one application.

Do I need a separate database too? No, Meilisearch is the system of record for whatever you index into it, though most setups treat it as a derived search index kept in sync with a primary database, rather than the only copy of the data.

How big can an index get on a CX23? There’s no hard limit in Meilisearch itself; the practical ceiling is RAM and disk on the server, a few hundred thousand documents of typical size is comfortable on 4 GB, well beyond that is where resizing up genuinely helps.

Is the REST API the only way in? No, official SDKs exist for JavaScript, Python, PHP, Ruby, Go, Rust and more, all of which call the same REST API this guide uses directly with curl.

Once the master key, HTTPS and a backup are in place, Meilisearch mostly runs itself. The two things worth revisiting later are a scoped key per application instead of the master key everywhere, and the backup schedule from Step 5 once the index is something you’d actually miss.

Why people pick Railyard for this

  • Your own cloud account. Deploy to DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, or any Linux server over SSH, billed directly by your provider with no markup.
  • One flat price per team. Free is free forever: one server plus a database server, three apps, no card. Pro is $19 a month for up to five servers and unlimited apps. Team is $49 a month with unlimited servers. No per-app, per-deploy, per-seat or per-gigabyte charges.
  • One-click templates. Deploy open-source apps, like databases, analytics and dev tools, to your own server in one click.
  • Managed databases. Postgres, MySQL and Redis with scheduled, restore-verified backups and point-in-time recovery.
  • Push-to-deploy. Deploy from GitHub, GitLab or the CLI, with automatic HTTPS and zero-downtime releases.

Deploy Meilisearch in one click

Railyard's Meilisearch template runs the official image for you, so you only add the master key and your indexes.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.