Deployment

Start with one.
Add nodes when you need them

All the documentationEvery part of the site, and the deep reference

The same binary serves both shapes. There is no separate cluster build and no coordinator process to deploy alongside it.

Single-node

Embedded storage, everything in one process. Suits edge deployments, development, and the large majority of production deployments.

Multi-node cluster

Consistent hashing places data, a Kademlia DHT discovers peers, and queries scatter-gather across the mesh. Nodes join without an election.

ROUTING 01

Decision logic

The receiving node decides locally whether it owns the key or forwards it.

ROUTING 02

Read workflow

Searches fan out in parallel; partial results merge on the way back.

ROUTING 03

Bulk write

Writes group by owning node so each batch commits once.

Containers

A distroless image
multi-platform

The published image carries the server, the CLI and the MCP server, the same as the binary. It is distroless and runs as a non-root user, so there is no shell inside it to attack. 0.3.8 is the current release and the one to run: it is where the hardening and stability work landed.

docker
# the image runs as uid 65532, so the data directory has to be its own
mkdir -p $(pwd)/data/cameodb
chown -R 65532:65532 $(pwd)/data/cameodb

docker run -d --name cameodb-server \
  -p 9480:9480 -p 9580:9580 \
  -v $(pwd)/data/cameodb:/data/cameodb \
  goranc/cameodb:0.3.8

Two ports

9480 carries the HTTP API and the MCP endpoint. 9580 is how nodes find each other, and a single node never needs it published.

Config and data, nothing else

Mount a TOML file at /etc/cameodb/cameodb.toml read-only and a data directory at /data/cameodb. No certificates and no keys belong in the image.

Digests, never keys

A node authenticates against SHA-256 digests of API keys, so CAMEODB_API_KEY_HASH goes in the environment and the key itself never does.

The client rides along

The same image is the CLI. Run it with --network host and client --interactive to get a REPL against a running node. :latest tracks the same build if you would rather not pin.

A three-node cluster is a compose file away: each node takes CAMEODB_SEED_NODES and CAMEODB_CLUSTER_NODES and finds the rest of the mesh itself. /_cluster/health stays reachable without a credential even when authentication is on, so a health probe needs no secret of its own.

Services

A service
on every platform

The deb and rpm packages run CameoDB under systemd; the msi installs it as a Windows service that starts with Windows and restarts itself after a failure. Config and data carry over through every upgrade.

Linux

cameodb.service runs as the cameodb user, with its config at /etc/cameodb/cameodb.toml and data under /var/lib/cameodb. systemctl reload cameodb picks up new keys without a restart.

Windows

The CameoDB service runs as its own virtual account, NT SERVICE\CameoDB, with nothing to manage. Config, data and logs live under C:\ProgramData\CameoDB.

A [logging] section adds a daily log file wherever you want one, and the msi sets it up for you under C:\ProgramData\CameoDB\logs. Install paths and service commands are on the downloads page.

Security posture

The profile is a claim
the config must meet

A profile is not a preset that rewrites your settings. It states who can reach this node, and a node whose configuration contradicts its own claim refuses to start rather than coming up quietly weaker than you thought.

Requirementlocalinternalexternal
Reachable fromthis machinetrusted networkanywhere
Bind addressloopback onlyanyany
TLSoptionalwarned if offrequired
CORS *warnedrejectedrejected
/_admin/*allowedallowedmust be off
Cluster PSKwarnedrequiredrequired
Authenticationoptionalwarned if offrequired

Choose by who can reach the bind address, not by what the environment is for: a shared test box is internal, not local. Omitting the profile is valid only for a loopback bind, which infers local; a node other hosts can reach has to state its posture.

Keys and limits

Who, what
and how often

Authentication answers who is calling. allowed_indexes answers what they may reach. Neither says anything about how often, and the caller that matters there is not an attacker.

Four capabilities

read, write, index-admin and node-admin, bundled as reader, writer and admin. A key may also be pinned to named indexes, and is refused on any other.

Digests, never keys

A node stores SHA-256 digests of the keys it will accept. Mint one with cameodb keygen, give the node the digest, and rotation never involves handing a secret to a config file.

An audit trail

[security.audit] records what was called and by which key, readable back from /_admin/audit rather than only from disk.

Tenants and write meters

A key can carry a tenant, and [security.tenants] sets how many indexes and how many bytes that tenant may hold. index_overrides gives a key a narrower role on named indexes. Writes are metered in documents, so a fair share means the same thing for every caller.

Rate limiting exists for the legitimate caller: a reader key held by an agent that decides to call search_across_indexes in a loop. Every one of those calls is authorized and every one fans out across every shard, so tool_calls_per_minute, max_search_limit and max_federated_indexes bound the cost of being useful.

Under load

Reads keep flowing
during bulk writes

The architectural choice that matters most in production is the strict separation of async and blocking work.

The spawn_blocking firewall

Storage I/O against Redb and Tantivy is confined to blocking thread pools. The Tokio and Axum runtime stays purely async, so heavy ingestion cannot stall HTTP request handling.

Commits on a clock

A write becomes searchable at the next commit, which runs on a steady commit_interval_ms while writes arrive and once more when they settle. No write ever waits for a commit.

Actor isolation

A Kameo actor model contains faults: a failing component does not take the process down with it.

Cache sized per shard

Each shard’s Redb cache is sized by tier from its data and kept within its share of the node’s memory, so a node holds many shards comfortably.

A writer thread per shard

Each shard owns its writer and is pinned to its own core, so parallel ingest never contends for one. The core budget follows a container’s CPU quota rather than the host’s core count.

Shutdown in five phases

A stop closes MCP sessions, drains HTTP connections, commits pending Tantivy writes and flushes the Redb WAL on every shard, stops the read pool, then the coordinator. The next boot starts clean, with nothing to replay.

Back in milliseconds

A node that stops unexpectedly reopens every shard in milliseconds, whatever its size. Each commit records Redb’s allocator state, so there is nothing to rebuild on the way back up.

Where a search’s time goes

Every search answer carries stats.timings_ms: parse, segment walk, resolve, fetch and build on the slowest shard, plus the node’s own merge. Shards answer with keys first and only the documents on the page are fetched, so deep pages and large limits stay fast.

Disk

Deletes give
the disk back

cameodb compact hands the space freed by deletes back to the disk, across every shard, and --dry-run shows what it will reclaim first. For the search index, a merge folds each shard’s segments into one while writes and searches carry on.

terminal
# the store, with the node stopped
sudo systemctl stop cameodb
sudo -u cameodb cameodb compact -c /etc/cameodb/cameodb.toml --dry-run
sudo -u cameodb cameodb compact -c /etc/cameodb/cameodb.toml
sudo systemctl start cameodb

# the search index, with the node running
curl -s -X POST http://localhost:9480/_admin/index/books/merge

Ctrl+C is safe: the next run picks up the files it has not reached. For housekeeping on a schedule, scripts/ops/retention-prune.sh in the repository clears out documents older than a cutoff, in steps you can review before anything is deleted.