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.
Decision logic
The receiving node decides locally whether it owns the key or forwards it.
Read workflow
Searches fan out in parallel; partial results merge on the way back.
Bulk write
Writes group by owning node so each batch commits once.
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.
# 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.
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.
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.
*warnedrejectedrejected/_admin/*allowedallowedmust be offChoose 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.
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.
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.
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.
# 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.