Databases AWS

Manticore Search on AWS User Guide

| Product: Manticore Search on AWS

Overview

Manticore Search is an open source search database. It indexes documents in real time and answers full text, vector and hybrid queries with relevance ranking, faceting and a SQL interface, and it began life as a fork of Sphinx. It is built for the work a general purpose database does badly: relevance ranked text retrieval over a product catalogue or a document corpus, log and event search, and semantic retrieval over embeddings.

The cloudimg image installs Manticore Search from the project's own signed package repository, pinned to an exact version whose published checksum is verified before installation. The important difference from a stock installation is what is exposed. Manticore performs no authentication of its own, because upstream expects it to sit on a trusted network, so this image never puts it on one that is not: the SQL port, the HTTP port and the binary API port are all bound to 127.0.0.1, and nginx on port 80 is the only network facing surface. nginx enforces HTTP basic authentication with a 24 character password generated uniquely on each instance's first boot, so no credential is baked into the image and no two instances share one. A separate unauthenticated health endpoint is provided for load balancer probes.

Manticore Search services and loopback bound listeners

What is included:

  • Manticore Search 28.4.4, installed from the official signed upstream apt repository with the package checksum verified at build time and the version held so it cannot drift
  • The complete bundle: the searchd daemon, the Manticore Columnar Library, secondary indexes, KNN vector indexing, the embeddings component, Manticore Buddy and the command line tools
  • manticore.service running as the dedicated non root manticore user with every listener bound to 127.0.0.1
  • nginx.service publishing the HTTP search API on port 80 behind HTTP basic authentication
  • manticoresearch-firstboot.service generating a unique 24 character basic authentication password on first boot, stored bcrypt hashed
  • A demo table (demo_articles) indexed in the image, so a relevance ranked query works on the first boot
  • A dedicated EBS volume mounted at /var/lib/manticore by filesystem identifier, holding every table, index and the binary transaction log
  • An unauthenticated /healthz endpoint for load balancer and liveness probes
  • Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
  • 24/7 cloudimg support

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and inbound port 80 from the networks that will query the search API
  • The AWS CLI (version 2) installed locally if you plan to deploy from the command line

Connecting to your instance

Connect over SSH on port 22 as the default login user for the operating system variant you launched. This listing currently ships the following variant:

OS variant SSH login user
Ubuntu 24.04 LTS ubuntu

The search API is served on port 80 through nginx. Manticore itself listens only on 127.0.0.1 ports 9306 (SQL), 9308 (HTTP) and 9312 (binary API).

Step 1: Launch the Instance from the AWS Marketplace

Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for Manticore Search. Select the cloudimg listing and choose Select, then Continue on the subscription summary.

Pick an instance type of m5.large or larger. Search is memory sensitive, so the more of your working set that fits in RAM the fewer disk reads each query needs; size up as your corpus grows. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that allows inbound port 22 from your management network and inbound port 80 from the networks that will query the search API. Leave the root volume at the default size or larger; the search index lives on its own separate volume, which you can size independently at launch.

Select Launch instance. First boot initialisation takes under a minute after the instance state becomes Running and the status checks pass.

Step 2: Launch the Instance from the AWS CLI

The following block launches an instance from the cloudimg Manticore Search Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22 and 80 as described above.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <key-name> \
  --subnet-id <subnet-id> \
  --security-group-ids <security-group-id> \
  --metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=manticore-search}]'

The image resolves this instance's public address on first boot using the EC2 Instance Metadata Service (IMDSv2), so it works with HttpTokens=required enabled. That address is written into the credentials note as the endpoint you query.

Step 3: Connect via SSH

Connect as the ubuntu user (see the Connecting to your instance table above for other variants).

ssh ubuntu@<public-ip>

The message of the day prints the endpoint and the key operational commands as soon as you log in.

Step 4: Retrieve the per instance credentials

First boot generates a unique basic authentication password for this instance and writes it, with the resolved endpoint URL, to a root only file. Nothing secret ships inside the image, so this file is the only place the credential exists.

sudo cat /root/manticore-credentials.txt

Real output from a running instance, with the password redacted:

# Manticore Search - generated on first boot by manticoresearch-firstboot.service.
# These credentials are unique to this instance. Store them somewhere safe.

MANTICORE_URL=http://<public-ip>/
MANTICORE_USERNAME=admin
MANTICORE_PASSWORD=<MANTICORE_PASSWORD>

The file is readable by root only:

sudo stat -c '%a %U:%G %n' /root/manticore-credentials.txt

Real output from a running instance:

600 root:root /root/manticore-credentials.txt

Step 5: Confirm the services are running

Manticore and nginx start automatically after the first boot service has generated the per instance password. Confirm both are active:

systemctl is-active manticore nginx

Real output from a running instance:

active
active

Step 6: Confirm the search database is bound to loopback only

This is the security property that matters most on this image. Manticore has no authentication of its own, so it must never be reachable from the network directly. Confirm every Manticore port is on 127.0.0.1 and that only nginx is listening on the public interface:

ss -ltn | grep -E '9306|9308|9312|:80 '

Real output from a running instance:

LISTEN 0      5          127.0.0.1:9312       0.0.0.0:*
LISTEN 0      5          127.0.0.1:9306       0.0.0.0:*
LISTEN 0      5          127.0.0.1:9308       0.0.0.0:*
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*
LISTEN 0      511             [::]:80            [::]:*

Port 9306 is the SQL interface over the MySQL wire protocol, 9308 is the HTTP and JSON search API and 9312 is the binary API. All three are loopback only. Port 80 is nginx.

Step 7: Confirm the search API refuses unauthenticated requests

An unauthenticated request to the search API must be rejected. The following check succeeds when the request is correctly refused:

if curl -s --fail -o /dev/null -m 10 -X POST 'http://127.0.0.1/sql?mode=raw' --data-urlencode 'query=SHOW TABLES'; then
  echo "UNEXPECTED: the search API answered without a credential"
  exit 1
else
  echo "refused - correct, the search API requires the per instance credential"
fi

A request carrying the wrong password is refused the same way:

if curl -s --fail -o /dev/null -m 10 -u 'admin:definitely-wrong-pw' -X POST 'http://127.0.0.1/sql?mode=raw' --data-urlencode 'query=SHOW TABLES'; then
  echo "UNEXPECTED: a wrong password was accepted"
  exit 1
else
  echo "refused - correct, only this instance's generated password is accepted"
fi

The unauthenticated health endpoint, which exists for load balancers, is served:

curl -s -o /dev/null -w 'GET /healthz -> HTTP %{http_code}\n' http://127.0.0.1/healthz

Real output from a running instance:

GET /healthz -> HTTP 200

Manticore Search authentication gate and real time indexing round trip

Step 8: Run your first query

Manticore exposes SQL over HTTP at two endpoints. /cli returns the familiar table formatting, which is ideal from a terminal, and /sql?mode=raw returns JSON, which is what an application uses. Both sit behind the same basic authentication gate.

The examples below read the password straight from the root only file into a shell variable, so it is never typed or echoed.

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
OUT=$(curl -s -u "admin:$P" -d 'SHOW TABLES' http://127.0.0.1/cli)
echo "$OUT"
echo "$OUT" | grep -q 'demo_articles' || { echo "the search API did not return the demo table"; exit 1; }

Real output from a running instance:

+---------------+------+
| Table         | Type |
+---------------+------+
| demo_articles | rt   |
+---------------+------+
1 row in set (0.003 sec)

demo_articles is a demo table indexed into the image so that search works before you load any data of your own. Inspect its schema:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$P" -d 'DESC demo_articles' http://127.0.0.1/cli

Real output from a running instance:

+-----------+-----------+----------------+
| Field     | Type      | Properties     |
+-----------+-----------+----------------+
| id        | bigint    |                |
| title     | text      | indexed stored |
| content   | text      | indexed stored |
| category  | string    |                |
| author    | string    |                |
| published | timestamp |                |
+-----------+-----------+----------------+
6 rows in set (0.001 sec)

title and content are full text fields, so they are tokenised and indexed for MATCH(). category, author and published are attributes, so they are available for filtering, grouping and sorting.

Manticore Search version and demo table schema

Step 9: Relevance ranked full text search

MATCH() runs the full text query and WEIGHT() exposes the relevance score, so you can order by it:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
HITS=$(curl -s -u "admin:$P" -d "SELECT id, title, WEIGHT() AS relevance FROM demo_articles WHERE MATCH('vector search') ORDER BY WEIGHT() DESC LIMIT 4" http://127.0.0.1/cli)
echo "$HITS"
echo "$HITS" | grep -qi 'vector' || { echo "the full text query returned no matching document"; exit 1; }

Real output from a running instance:

+---------------------+-------------------------------------------------+-----------+
| id                  | title                                           | relevance |
+---------------------+-------------------------------------------------+-----------+
| 7930166842906116098 | Vector search and KNN in a search database      | 4500      |
| 7930166842906116103 | Migrating from Sphinx to Manticore Search       | 3473      |
| 7930166842906116099 | Hybrid search combining keywords and embeddings | 2482      |
+---------------------+-------------------------------------------------+-----------+
3 rows in set (0.001 sec)

The documents come back ranked, not merely matched. The full text query language supports phrases ("vector search"), proximity ("vector search"~5), field limits (@title vector), boolean operators and wildcards.

Faceted counts are computed in the same round trip as the search, which is how a storefront renders its filter sidebar without extra queries:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$P" -d 'SELECT category, COUNT(*) AS docs FROM demo_articles GROUP BY category ORDER BY docs DESC' http://127.0.0.1/cli

Real output from a running instance:

+------------+------+
| category   | docs |
+------------+------+
| search     | 5    |
| operations | 2    |
| indexing   | 1    |
| sql        | 1    |
| migration  | 1    |
+------------+------+
5 rows in set (0.001 sec)

Manticore Search relevance ranked query and faceted counts

Step 10: Create your own table and index in real time

A real time table accepts writes and makes them searchable immediately. There is no offline build step and no rebuild window. The following creates a table, indexes a document into it and searches it back:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$P" -d 'DROP TABLE IF EXISTS quickstart' http://127.0.0.1/cli
curl -s -u "admin:$P" -d 'CREATE TABLE quickstart (title text, body text, tag string)' http://127.0.0.1/cli
curl -s -u "admin:$P" -d "INSERT INTO quickstart (title, body, tag) VALUES ('Manticore on AWS','real time indexing makes this document searchable immediately','guide')" http://127.0.0.1/cli
FOUND=$(curl -s -u "admin:$P" -d "SELECT id, title FROM quickstart WHERE MATCH('real time indexing')" http://127.0.0.1/cli)
echo "$FOUND"
echo "$FOUND" | grep -q 'Manticore on AWS' || { echo "the document was not searchable after the insert"; exit 1; }

Real output from a running instance:

Query OK, 0 rows affected (0.001 sec)

Query OK, 0 rows affected (0.003 sec)

Query OK, 1 row affected (0.003 sec)

+---------------------+------------------+
| id                  | title            |
+---------------------+------------------+
| 7930166845154263041 | Manticore on AWS |
+---------------------+------------------+
1 row in set (0.001 sec)

The document was searchable on the query immediately after the insert. Remove the scratch table when you have finished experimenting:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$P" -d 'DROP TABLE IF EXISTS quickstart' http://127.0.0.1/cli
curl -s -u "admin:$P" -d 'SHOW TABLES' http://127.0.0.1/cli

Real output from a running instance:

Query OK, 0 rows affected (0.005 sec)

+---------------+------+
| Table         | Type |
+---------------+------+
| demo_articles | rt   |
+---------------+------+
1 row in set (0.001 sec)

Step 11: Query the search API from your application

From outside the instance, point your application at port 80 with the generated credential. The JSON endpoint is /sql?mode=raw, which returns the result set as JSON rather than a formatted table. Replace <password> with the value from Step 4.

curl -u 'admin:<password>' \
  --data-urlencode "query=SELECT id, title FROM demo_articles WHERE MATCH('hybrid search')" \
  'http://<public-ip>/sql?mode=raw'

Manticore also exposes a JSON search API for applications that prefer a document oriented request body, at /search, /insert, /replace, /update, /delete and /bulk, and an Elasticsearch compatible surface at the root path. All of them are behind the same basic authentication gate on port 80.

Because Manticore itself performs no authentication, treat the basic authentication credential as the only thing between the internet and your index. Restrict port 80 to the networks that need it in your security group, and put TLS in front of the endpoint before serving real traffic.

Step 12: Reach the MySQL protocol port over an SSH tunnel

Port 9306 speaks the MySQL wire protocol, so any MySQL client or driver can query the search database directly. It is deliberately bound to loopback and is not exposed to the network. Forward it over your SSH session instead, from your workstation:

ssh -N -L 9306:127.0.0.1:9306 ubuntu@<public-ip>

With the tunnel open, connect from a second terminal on your workstation with any MySQL client. Manticore does not use MySQL user accounts, so no username or password is required on this port; the SSH tunnel is the access control.

mysql -h 127.0.0.1 -P 9306

Then issue ordinary SQL, for example SHOW TABLES; or SELECT id, title FROM demo_articles WHERE MATCH('faceted search');. The same interface is what MySQL compatible drivers, ORMs and BI tools use to talk to Manticore.

Where your data lives

Every table, its index files, the document store and the binary transaction log live in /var/lib/manticore, which is a dedicated EBS volume mounted by filesystem identifier. Keeping the index off the operating system disk means it can be resized on its own as the corpus grows.

df -h /var/lib/manticore /

Real output from a running instance:

Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme1n1     59G  100K   56G   1% /var/lib/manticore
/dev/root        38G  3.5G   35G  10% /

To grow the index volume, modify the EBS volume in the EC2 console or with aws ec2 modify-volume, then extend the filesystem on the instance with sudo resize2fs against the device shown in the Filesystem column above. No downtime is required and the fstab entry needs no change, because it references the filesystem identifier rather than a device name.

Putting TLS in front of the search API

The image publishes the search API over plain HTTP on port 80 so that it works the moment the instance boots, with no certificate to obtain first. For production, terminate TLS in front of it. Two common approaches:

  • Put the instance behind an Application Load Balancer with an AWS Certificate Manager certificate, and restrict the instance security group so port 80 accepts traffic only from the load balancer's security group.
  • Obtain a certificate for a DNS name you control and terminate TLS on the instance itself in the nginx server block at /etc/nginx/sites-available/cloudimg-manticore, adding a listen 443 ssl; server and redirecting port 80 to it.

Validate any nginx change before applying it:

sudo nginx -t

Real output from a running instance:

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

If the configuration test passes, apply it with sudo systemctl reload nginx.

Changing the search API password

The per instance credential lives in an nginx htpasswd file as a bcrypt hash. To rotate it, generate a new entry and reload nginx. Replace <new-password> with your own value.

sudo htpasswd -B -c /etc/nginx/.manticore.htpasswd admin
sudo chown root:www-data /etc/nginx/.manticore.htpasswd
sudo chmod 0640 /etc/nginx/.manticore.htpasswd
sudo systemctl reload nginx

htpasswd prompts for the new password rather than taking it on the command line, so it is never written to your shell history. Remember to update /root/manticore-credentials.txt afterwards so the note stays accurate.

Maintenance

Check the installed version:

searchd --version

Real output from a running instance:

Manticore 28.4.4 f63d06ecb@26071006 (columnar 13.8.1 dd160f1@26070613) (secondary 13.8.1 dd160f1@26070613) (knn 13.8.1 dd160f1@26070613) (embeddings 1.1.1 dd160f1@26070613)

Check the daemon's own view of its state:

P=$(sudo grep '^MANTICORE_PASSWORD=' /root/manticore-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$P" -d "SHOW STATUS LIKE 'uptime'" http://127.0.0.1/cli

Read the daemon log if a query behaves unexpectedly:

sudo tail -5 /var/log/manticore/searchd.log

The Manticore package is held at the shipped version so an unattended upgrade cannot move you off it unexpectedly. To upgrade deliberately, release the hold, upgrade and re-apply it:

sudo apt-mark unhold manticore
sudo apt-get update && sudo apt-get install --only-upgrade manticore
sudo apt-mark hold manticore
sudo systemctl restart manticore

Operating system security updates are applied automatically by unattended upgrades.

Manticore ships a backup tool that takes a consistent copy of your tables while the daemon is running:

sudo manticore-backup --backup-dir=/var/backups --all

Support

cloudimg provides 24/7 technical support for this image via email and live chat.

  • Email: support@cloudimg.co.uk
  • Live chat: available 24/7 on cloudimg.co.uk

We help with deployment, schema and index design, relevance tuning, vector and hybrid search setup, bulk loading, replication, backup, TLS termination, storage planning and upgrades.