How to Install Meilisearch with Docker and Build a Private Search API on Ubuntu

Updated on Aug 24, 2026
Mila H
8 MINS READ
Table of Contents
Build a Private Search API with Meilisearch

If you want to build a private search API with Meilisearch, this guide provides a full setup. With Meilisearch, you get full control over your data, zero usage limits, and a search engine that runs entirely on your own server.

What Is Meilisearch?

Meilisearch is an open-source, self-hosted search engine built for speed and simplicity. It returns results as you type, handles typos, and integrates easily with any app via its REST API.

Meilisearch is a great fit for:

  • E-commerce product search.
  • Documentation sites with a search bar.
  • SaaS apps that need internal content search.
  • Any self-hosted app where you do not want to pay for Algolia or Elasticsearch.

If you are looking for a reliable place to run Meilisearch, a flexible Linux VPS is a good option.

Prerequisites to Build a Private Search API with Meilisearch

Before you begin, make sure you have the following:

  • Ubuntu 22.04 or 24.04 server.
  • A non-root user with sudo access.
  • At least 1 vCPU, 1 GB RAM, and 10 GB disk space.
  • Docker and Docker Compose are installed on your server.
  • A domain name or IP address pointing to your server.

In this guide, we assume you have Docker and Docker Compose installed and ready. Proceed to the following steps to build a private search API with Meilisearch.

Step 1. Generate a Strong Meilisearch Master Key

First, run the system update and make sure Docker is running:

Bash
apt updatedocker run hello-world

If you see the Hello World message, Docker is ready.

Create a project directory to keep everything organized and switch to it:

Bash
mkdir -p ~/meilisearch && cd ~/meilisearch

The master key is the most important security setting in Meilisearch. Never run Meilisearch without it, especially in production. Use openssl to generate a random, secure key:

Bash
openssl rand -hex 32

Copy the output; you will need it for the next steps.

Important Note: The master key must be at least 16 bytes. A 32-byte hex string is a solid choice. Keep it secret, it gives full control over your Meilisearch instance.

Step 2. Create the Meilisearch Environment File

You can easily store secrets in a .env file instead of hardcoding them into your Docker Compose YAML file.

Create the file with your desired text editor:

Bash
nano .env

Paste the following variables and replace the value with your generated key:

Bash
MEILI_MASTER_KEY=your_generated_master_key_hereMEILI_ENV=productionMEILI_NO_ANALYTICS=true
  • MEILI_ENV=production enables authentication and disables the open search preview UI.
  • MEILI_NO_ANALYTICS=true stops Meilisearch from sending anonymous usage data.

Step 3. Write the Meilisearch Docker Compose File

At this point, you can define your service, volumes, and network. To build a private search API with Meilisearch cleanly, create the Docker Compose YAML file with:

Bash
nano docker-compose.yml

Paste this configuration into the file:

YAML
services:  meilisearch:    image: getmeili/meilisearch:v1.8    container_name: meilisearch    restart: unless-stopped    ports:      - "127.0.0.1:7700:7700"    environment:      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY}      - MEILI_ENV=${MEILI_ENV}      - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS}    volumes:      - meili_data:/meili_data    networks:      - search_net    healthcheck:      test: ["CMD", "wget", "--no-verbose", "--spider", "http://localhost:7700/health"]      interval: 10s      timeout: 5s      retries: 5 volumes:  meili_data: networks:  search_net:    driver: bridge

Key things include:

  • 127.0.0.1:7700:7700: Binds the port to localhost only, so the API is not directly reachable from the internet. Traffic goes through nginx instead.
  • meili_data:/meili_data: Uses a Docker-managed named volume for persistent storage. Your indexes survive container restarts and updates.
  • restart: unless-stopped: The container restarts automatically on reboot or crash.
  • healthcheck: Docker will monitor the /health endpoint and show the container as unhealthy if Meilisearch is not responding.

Step 4. Start Meilisearch

Once you are done, use the command below to start Meilisearch:

Bash
docker compose up -d

Start Meilisearch

Then, use the commands below to check that it is running:

Bash
docker compose psdocker compose logs meilisearch

Check Meilisearch docker container status

From the logs, you should see output confirming Meilisearch has started and a master key is set.

Also, test the health endpoint from the server itself:

Bash
curl http://localhost:7700/health

In the output, you must see:

JSON
{  "status": "available"}

Step 5. Retrieve Your Meilisearch API Keys

When you start Meilisearch with a master key, it automatically generates default API keys. You must use these for the operations, never the master key directly.

To fetch them, use the command below with your master key:

Bash
curl -X GET 'http://localhost:7700/keys' \  -H "Authorization: Bearer YOUR_MASTER_KEY"

You will get back four default keys:

Key Name Purpose Use From
Default Search API Key Search queries only Frontend and client apps
Default Admin API Key Full access, except key management Backend and server only
Default Read-Only Admin API Key Read-only access Monitoring tools
Default Chat API Key Conversational search Frontend

Rule of thumb: Use the Admin API Key when indexing data or changing settings. Use the Search API Key in your app's frontend or public-facing search widget. Never expose the master key in code.

Step 6. Create a Products Index and Add Documents

At this point, you can build a private search API with Meilisearch by creating a real index with sample product data.

Use the command below to create a products index:

Bash
curl -X POST 'http://localhost:7700/indexes' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY" \  -H 'Content-Type: application/json' \  -d '{    "uid": "products",    "primaryKey": "id"  }'

Add documents to the index:

Bash
curl -X POST 'http://localhost:7700/indexes/products/documents' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY" \  -H 'Content-Type: application/json' \  -d '[    {      "id": 1,      "name": "Wireless Keyboard",      "description": "Compact Bluetooth keyboard with backlight",      "category": "Electronics",      "price": 49.99    },    {      "id": 2,      "name": "Mechanical Mouse",      "description": "Ergonomic gaming mouse with RGB lighting",      "category": "Electronics",      "price": 39.99    },    {      "id": 3,      "name": "USB-C Hub",      "description": "7-in-1 hub with HDMI, USB 3.0, and SD card reader",      "category": "Accessories",      "price": 29.99    }  ]'

Meilisearch processes document additions asynchronously. It returns a task ID immediately. You can check the task status:

Bash
curl -X GET 'http://localhost:7700/tasks/0' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY"

When the status shows "succeeded", the documents are indexed and ready.

Then, run a search query with:

Bash
curl -X POST 'http://localhost:7700/indexes/products/search' \  -H "Authorization: Bearer YOUR_SEARCH_API_KEY" \  -H 'Content-Type: application/json' \  -d '{"q": "keyboard"}'

You will get back the matching products instantly. Even try a typo like "keybord", Meilisearch handles it.

Step 7. Configure Meilisearch Searchable Attributes

By default, Meilisearch searches all fields. In production, it is better to tell it which fields matter:

Bash
curl -X PUT 'http://localhost:7700/indexes/products/settings/searchable-attributes' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY" \  -H 'Content-Type: application/json' \  -d '["name", "description", "category"]'

Also, you can set filterable attributes for faceted search:

Bash
curl -X PUT 'http://localhost:7700/indexes/products/settings/filterable-attributes' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY" \  -H 'Content-Type: application/json' \  -d '["category", "price"]'

This allows queries like: search for "mouse" but only in the Electronics category.

Build a Private Search API with Meilisearch Behind Nginx

Exposing port 7700 directly to the internet is risky. The right way to build a private search API with Meilisearch for production use is to put it behind a reverse proxy.

Install Nginx with the command below:

Bash
apt install nginx -y

Create the Nginx config file for your domain:

Bash
nano /etc/nginx/sites-available/meilisearch

Paste this plain HTTP config with your actual domain:

Bash
server {    listen 80;    server_name your-domain.com;     location / {        proxy_pass http://127.0.0.1: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;    }}

Enable the site and test the config:

Bash
ln -s /etc/nginx/sites-available/meilisearch /etc/nginx/sites-enabled/nginx -t

You should see:

Bash
nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successful

Reload Nginx to apply the changes:

Bash
sudo systemctl reload nginx

Make sure port 80 is open in your firewall:

Bash
sudo ufw allow 'Nginx Full'sudo ufw allow OpenSSHsudo ufw enable

Then, install Certbot and the Nginx plugin and run Certbot:

Bash
sudo certbot --nginx -d your-domain.com

Certbot will ask for your email address and prompt you to agree to the terms. It will then:

  • Verify that your domain points to the server.
  • Obtain a free Let's Encrypt certificate.
  • Automatically update your Nginx config to add SSL.

Verify SSL is working:

Bash
curl https://your-domain.com/health

You should get back:

JSON
{  "status": "available"}

Test automatic certificate renewal:

Bash
sudo certbot renew --dry-run

Certbot sets up a cron job or systemd timer to renew the certificate automatically before it expires. You do not need to do anything manually after this point.

Note: If you want to build a private search API with Meilisearch that stays private, block direct access to port 7700 from the outside world. The API should only be reachable through Nginx:

Bash
sudo ufw deny 7700sudo ufw status

Keep Your Meilisearch Data Safe with Backups

Data in a Docker named volume (meili_data) survives container restarts. But for real backup protection, you should export dumps regularly.

Create a dump with the command below:

Bash
curl -X POST 'http://localhost:7700/dumps' \  -H "Authorization: Bearer YOUR_ADMIN_API_KEY"

Meilisearch saves the dump inside the volume at /meili_data/dumps/. You can mount this path and copy dumps to external storage or an S3-compatible bucket on a schedule.

View available dumps:

Bash
docker exec meilisearch ls /meili_data/dumps/

How To Update Meilisearch

To update to a newer version, just change the image tag in your docker-compose.yml, then restart:

Bash
docker compose pulldocker compose up -d

Because you are using a named volume, your data is not lost during updates. Always read the Meilisearch release notes before updating in production; major versions sometimes require a dump migration.

For more information, you can visit the Meilisearch Official Documentation.

Conclusion

Now you have a fully working Meilisearch deployment on Ubuntu. This is how you build a private search API with Meilisearch the right way, not just on localhost for development, but as a real deployable service.

The setup is minimal enough to run on a single VPS. Once your data grows and query volume increases, the natural step is to move to a dedicated server hosting where Meilisearch gets its own dedicated resources and does not share CPU or RAM with other services.

We hope you enjoy this guide.

Not at all. It runs well on 1 vCPU and 1 GB RAM for small to medium indexes. Larger datasets need more RAM since Meilisearch loads indexes into memory.

Yes. You can install the binary directly or use a package manager. Docker is just the easiest way to deploy and manage it.

Yes. Since it exposes a REST API, any language that can make HTTP requests works. Official SDKs exist for JavaScript, Python, PHP, Ruby, Go, and more.