Search on pgvector, in Containers
Two containers on one private network: PostgreSQL with the pgvector extension, and LM-Kit One using it as a search cluster, so indexed documents and their vectors live in a database you can back up, replicate and monitor with the PostgreSQL tooling you already have. About ten minutes, with Docker or Podman, on any host that runs containers. Every command below was run as written; where a step has depth, the guide that owns it is linked.
1Before you start#
- Docker (Docker Desktop on Windows or macOS) or Podman. The commands are identical in Podman:
replace
dockerwithpodman. - Two passwords you choose: one for the PostgreSQL superuser, one for LM-Kit One's
adminaccount. The commands carry the placeholdersyour-db-passwordandyour-admin-password; replace both. - A GPU is optional. Section 4 of Running in Containers has the one flag for NVIDIA and the one for AMD or Intel.
2Create the network#
docker network create lmkit-net
Containers on a user-defined network reach each other by container name, so LM-Kit One will
address the database as pgvector with nothing else configured, and the database never needs a
published port.
3Start PostgreSQL with pgvector#
docker run -d --name pgvector --network lmkit-net \
-e POSTGRES_PASSWORD=your-db-password \
-v pgvector-data:/var/lib/postgresql \
pgvector/pgvector:0.8.6-pg18-trixie
The image is the official PostgreSQL 18 image with pgvector 0.8.6 added
(pgvector on Docker Hub); a pg17 or pg16 tag
serves a shop whose tooling expects an older major. The volume mounts at /var/lib/postgresql,
where the PostgreSQL 18 image keeps its data, so the index survives replacing the container; a
mount anywhere else is ignored silently. There is no -p: the database is reachable from the
network only. For a shell on it, docker exec -it pgvector psql -U postgres.
4Start LM-Kit One on the same network#
docker run -d --name lmkit --network lmkit-net -p 5189:5189 -p 7221:7221 \
-e Admin__InitialPassword=your-admin-password \
-v lmkit-state:/data/state -v lmkit-models:/data/models \
lmkitone/lm-kit-one
Add --gpus all for an NVIDIA GPU, or --device /dev/dri for an AMD or Intel GPU on a Linux
host. Podman needs the NVIDIA toolkit's CDI specification before --gpus all resolves; section
4 of Running in Containers has the two commands. Open
https://localhost:7221/admin, accept the self-signed certificate once, and sign in as admin
with the password you seeded. Without the seed, the server prints a one-time password in
docker logs lmkit; section 1 of Running in Containers covers that
sign-in in full.
5Register the database as a search cluster#
In the panel, open Build > Search and click + Add cluster. A cluster is one search database, and the form creates it for you.
| Field | Value |
|---|---|
| Cluster ID | pgvector |
| Full-text store | PostgreSQL (external server) |
| Vector store | pgvector (same PostgreSQL) |
| Host | pgvector |
| Port | 5432 |
| Database | lmkit_search |
| Application user, and its password | lmkit_search_app, with a password you choose |
| Admin username, Admin password | postgres, your-db-password |
Click Test connection first. The reply reads: full-text (PostgreSQL) server reachable, the
database does not exist yet and will be created when you save the cluster, vectors (pgvector
0.8.6) ok. Then Create. LM-Kit One creates the database, installs the vector extension,
initializes the schema, creates the application login and signs in with it once, and the cluster
lists as Healthy beside the built-in local cluster. From here on the application user carries the
search traffic; the admin login served the provisioning alone. Open the cluster and click
Set default if requests that name no cluster should land here; otherwise pass cluster_id
in every call, as section 7 does.
6A tenant and a key#
A tenant is an isolated workspace inside the cluster, and an API key reaches only the clusters and tenants it was granted (Search: Access Model).
- On the cluster's page, click + Add tenant, name it
docs, and Create. The tenant's page shows its id; section 7 needs it. - In Settings > Access, open API access and click Create key. Name the key, set
Search access to Only pgvector, tick the
docstenant under Grant tenants in pgvector now, and Create key. Copy the token: it is shown once.
Create the key after the cluster exists. A key's cluster access is chosen when it is minted, and a key minted without it cannot reach a tenant it is later granted.
7Index one document and search it#
Three calls from the host, with the key and the tenant id from section 6. Collection and document ids are yours to choose; any UUID works, so the two below can be pasted as they are.
curl -s http://localhost:5189/lmkit/v1/search/collections \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"cluster_id":"pgvector","tenant_id":"TENANT_ID","collection_id":"00000000-0000-4000-8000-000000000001","display_name":"notes"}'
curl -s http://localhost:5189/lmkit/v1/search/documents \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"cluster_id":"pgvector","tenant_id":"TENANT_ID","collection_id":"00000000-0000-4000-8000-000000000001","document_id":"00000000-0000-4000-8000-000000000002","input":"LM-Kit One keeps this index in PostgreSQL with pgvector.","input_format":"PlainText","name":"first-note.txt"}'
curl -s http://localhost:5189/lmkit/v1/search/search \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"cluster_id":"pgvector","tenant_id":"TENANT_ID","collection_id":"00000000-0000-4000-8000-000000000001","query":"pgvector","search_type":"FullText"}'
The second call answers "outcome":"Created"; the third returns the document with the match
highlighted in its snippet, and the cluster page now counts one tenant, one collection and one
document. Full-text answers at once; the tenant's semantic index builds in the background once
its embedding model has downloaded, and hybrid search follows. Indexing real documents and
tuning quality is Indexing Well; queries, filters and reranking are
Querying and Relevance; cited answers over the corpus are
Grounded Answers.
8Keep it running#
- Backups.
docker exec pgvector pg_dump -U postgres lmkit_search > lmkit_search.sqldumps the index; thelmkit-statevolume is the installation itself (Backup and Upgrades). - Upgrades. Pull the new tag and recreate the container; both volumes stay.
- One node per state volume. A second LM-Kit One container must never mount the same
lmkit-state. Two nodes are a farm, with a shared identity store, and that is Scaling Out. - Compose. The same two services in one file; Compose puts them on a shared network by
itself, so
pgvectorresolves exactly as before, and sections 5 to 7 apply unchanged afterdocker compose up -d.
services:
pgvector:
image: pgvector/pgvector:0.8.6-pg18-trixie
container_name: pgvector
restart: unless-stopped
environment:
POSTGRES_PASSWORD: your-db-password
volumes:
- pgvector-data:/var/lib/postgresql
lmkit:
image: lmkitone/lm-kit-one
container_name: lmkit
restart: unless-stopped
depends_on:
- pgvector
ports:
- "5189:5189"
- "7221:7221"
environment:
Admin__InitialPassword: your-admin-password
volumes:
- lmkit-state:/data/state
- lmkit-models:/data/models
volumes:
pgvector-data:
lmkit-state:
lmkit-models: