Get a cluster running
Run the container image, or build from source with Cargo. There are no distro packages yet. Either way it is a couple of commands, plus a compile if you build.
Run with Docker
The quickest way to try it: one node in a container, with the DynamoDB endpoint and both
web consoles published on your machine. Generate a single-node config that binds every
interface (the image has no shell, so the config comes from the image's own
gen-config), then start the node with a named volume for its data.
$ docker run --rm ghcr.io/animus-db/animusd:latest gen-config --nodes 1 --host 0.0.0.0 > animus.json $ docker run -d --name animusd \ -p 7102:7102 -p 7103:7103 -p 7105:7105 \ -v animus-data:/var/lib/animus \ -v "$PWD/animus.json:/etc/animus/cluster.json:ro" \ ghcr.io/animus-db/animusd:latest \ --config /etc/animus/cluster.json --node 0 --dir /var/lib/animus
| Port | What it serves |
|---|---|
7102 | The DynamoDB endpoint: http://localhost:7102. |
7103 | The operator dashboard: http://localhost:7103. |
7105 | The data console: http://localhost:7105. |
Data lives in the animus-data volume, so it survives docker rm.
Once it logs ready, use the AWS CLI exactly as in
Connect to it, with ANIMUS=http://localhost:7102.
latest tracks the main branch; there are no version tags yet.
It is built for linux/amd64 only, so Apple-silicon Macs run it under
emulation. It is pre-alpha, and both consoles are unauthenticated: publish the ports
only on a machine you trust (use -p 127.0.0.1:7102:7102 to keep them
off the network).
Prefer to build it yourself, or want a three-node cluster in one process? Use the source build below.
Prerequisites
A Rust toolchain and Git. The repository pins its toolchain, so rustup
will fetch the right compiler automatically the first time you build.
- Rust 1.96 or newer — install with rustup.
- Git, and a machine with a few gigabytes free for the build.
- Linux or macOS. Windows is untested.
Optional, for talking to the database once it's up:
- The AWS CLI or any AWS SDK, for the DynamoDB endpoint.
Build from source
The alternative to the container image, and the way to run a multi-node cluster in one process.
$ git clone https://github.com/animus-db/animus-db.git $ cd animus-db $ cargo build --release
That produces two binaries in target/release:
| Binary | What it is |
|---|---|
animusd | The node server. One process per node; it hosts the control plane, the data plane and every wire endpoint. |
animus | The operator and client CLI — cluster status, simple get/put, and the whole admin subcommand tree. |
A debug build (cargo build) is fine for experimenting and compiles faster,
but is markedly slower at runtime. Use --release for anything you intend to measure.
Run a cluster
The fastest way to see AnimusDB work is a whole cluster in one process. Nodes bind OS-assigned ports and print them at startup.
$ ./target/release/animusd --cluster 3 animusd: started 3-node cluster (CP) node 0: client 127.0.0.1:52104 — dynamo http 127.0.0.1:52105 — admin http://127.0.0.1:52107 — console http://127.0.0.1:52109 node 1: client 127.0.0.1:52111 — dynamo http 127.0.0.1:52112 … node 2: client 127.0.0.1:52118 — dynamo http 127.0.0.1:52119 … animusd: ready — Ctrl-C to stop
State is written under a fresh, unique-per-process temporary directory by
default, so running this command again never picks up the previous run's
state. Pass --dir ./cluster-data to reuse a directory of your
choosing, or --ephemeral to also swap the tablet-data engine for
a volatile in-memory one.
Add --auto-split-bytes 67108864 to watch tablets split as they grow, which is
the interesting behaviour to poke at on a laptop.
Connect to it
Any node accepts any request and routes it internally, so you can talk to whichever one you like. The examples below use node 0's printed dynamo port.
The AWS CLI
SigV4 signature verification is opt-in and off by default, but the AWS CLI insists on credentials being present regardless — any placeholder values work unless you've turned verification on.
$ export AWS_ACCESS_KEY_ID=local AWS_SECRET_ACCESS_KEY=local AWS_REGION=us-east-1 $ export ANIMUS=http://127.0.0.1:52105 $ aws dynamodb create-table --endpoint-url $ANIMUS \ --table-name orders \ --attribute-definitions AttributeName=id,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST $ aws dynamodb put-item --endpoint-url $ANIMUS --table-name orders \ --item '{"id": {"S": "a-1"}, "total": {"N": "42"}}' $ aws dynamodb get-item --endpoint-url $ANIMUS --table-name orders \ --key '{"id": {"S": "a-1"}}' { "Item": { "id": { "S": "a-1" }, "total": { "N": "42" } } }
Python (boto3)
import boto3 db = boto3.resource( "dynamodb", endpoint_url="http://127.0.0.1:52105", region_name="us-east-1", aws_access_key_id="local", aws_secret_access_key="local", ) table = db.Table("orders") table.put_item(Item={"id": "a-2", "total": 99}) print(table.get_item(Key={"id": "a-2"})["Item"])
JavaScript (AWS SDK v3)
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"; const client = new DynamoDBClient({ endpoint: "http://127.0.0.1:52105", region: "us-east-1", credentials: { accessKeyId: "local", secretAccessKey: "local" }, }); await client.send(new PutItemCommand({ TableName: "orders", Item: { id: { S: "a-3" }, total: { N: "7" } }, }));
The animus CLI
# cluster status, against a node's client port $ ./target/release/animus status 127.0.0.1:52104 # raw key/value access, against a table $ ./target/release/animus put 127.0.0.1:52104 orders k1 hello $ ./target/release/animus get 127.0.0.1:52104 orders k1 # operator surface, against a node's admin port $ ./target/release/animus admin status 127.0.0.1:52107
Open the consoles
Two web UIs are compiled into animusd; neither needs a build step or an internet connection.
Operator dashboard
On the admin port. Cluster and node health, the tablet map, Raft internals, storage and WAL introspection, and gated operator actions like split, flush, drain and decommission.
Data console
On the console port. A DynamoDB-shaped app for application developers: browse tables, inspect and edit items, review a table's configuration and its stream.
Both are printed in the startup banner. Neither has authentication — see the security note. (SigV4 verification, when enabled, applies only to the DynamoDB client port.)
Run a real multi-node cluster
For separate machines, generate a config once, copy it to every node, and start one
process per node with a distinct --node index.
-
Generate a cluster config
$ animusd gen-config --nodes 3 --host 10.0.0.1 > cluster.json
The generated config lists every node's identity and its six listener addresses. Edit the hosts so each entry names the right machine, then copy the same file to all of them. Ports start at
--base-port(7100 by default). -
Start each node
# on the first machine $ animusd --config cluster.json --node 0 --dir /var/lib/animusdb # on the second, and so on $ animusd --config cluster.json --node 1 --dir /var/lib/animusdb $ animusd --config cluster.json --node 2 --dir /var/lib/animusdb
-
Check it came up
$ animus admin status 10.0.0.1:7103 $ animus admin health 10.0.0.1:7103
Or open the dashboard at
http://10.0.0.1:7103.
Add and remove nodes
A new node does not need the cluster config. Give it the intra-cluster address of any running node and it registers itself, learns the topology, and starts receiving tablet replicas.
# on the new machine — 7104 is node 0's intra port $ animusd join --seed 10.0.0.1:7104 --base-port 7100 --dir /var/lib/animusdb
Removing one is a drain followed by a decommission, so replicas move off before the node goes away:
$ animus admin drain 10.0.0.1:7103 n3 $ animus admin drain-status 10.0.0.1:7103 n3 $ animus admin decommission 10.0.0.1:7103 n3
Split control and data
Larger clusters usually want a small, stable control quorum that is not competing with storage for resources. Generate a split config and run each role explicitly.
$ animusd gen-config --control-nodes 3 --data-nodes 5 --host 10.0.0.1 > cluster.json # control-only: metadata quorum, no storage engine $ animusd control --config cluster.json --node 0 # data-only: hosts tablets, mirrors metadata from the control plane $ animusd data --config cluster.json --node 3 # or let a data node join by seed instead of by config $ animusd data --seed 10.0.0.1:7104 --base-port 7200
To try the shape without eight machines, animusd --cluster-control 3 --cluster-data 5
runs the whole split deployment in one process.
Ports
Each node owns a block of six consecutive ports, starting at its base port
(7100 for node 0 in a generated config, then 7106, 7112, and so on).
| Offset | Node 0 | Listener | Purpose | Exposure |
|---|---|---|---|---|
| +0 | 7100 | internal | Raft traffic between nodes | Cluster-internal |
| +1 | 7101 | client | The native length-prefixed protocol the animus CLI speaks | Trusted |
| +2 | 7102 | dynamo | DynamoDB JSON over HTTP, plus GET /metrics | Trusted |
| +3 | 7103 | admin | Admin JSON API and the operator dashboard | Cluster-internal |
| +4 | 7104 | intra | Node-to-node request forwarding and metadata mirroring; the address to --seed | Cluster-internal |
| +5 | 7105 | console | The data console (not bound on control-only nodes) | Cluster-internal |
Only the DynamoDB port can verify a caller, and only if you turn on SigV4 — off by default. Everywhere else "trusted" means a network you control. Keep the internal, intra, admin and console ports off any externally reachable address.
Common flags
| Flag | Effect |
|---|---|
--config FILE --node I | Run node I of a generated cluster config. The normal production invocation. |
--cluster N | Run an N-node cluster in a single process. Development convenience. |
--dir DIR | Where this node keeps its write-ahead logs, SSTables and snapshots. |
--ephemeral | Use a volatile in-memory engine for tablet data instead of the on-disk default. Control-plane state still lives under --dir for that run, but --cluster N/--cluster-control/--cluster-data default --dir to a fresh directory per process either way, so back-to-back runs never rehydrate a previous one. |
--auto-split-bytes B | Split a tablet once it exceeds B bytes. |
--quiesce-after SECS | How long a tablet group must be idle before it stops ticking. On by default. |
--no-heartbeat-batch | Turn off per-destination heartbeat batching (coalescing every co-hosted tablet group's heartbeat toward the same peer into one wire frame). On by default. |
--no-shared-wal | Turn off the shared write-ahead log (routing every co-hosted tablet group's writes through one per-node file with one coalesced fsync per round, instead of a private WAL file per group). On by default; flipping this against an existing data directory written under the other layout is a startup error, not a silent reset — start against a fresh --dir instead. |
--stream-seal-bytes B--stream-seal-age SECS--stream-retention SECS | When change-log segments are sealed into stream shards, and how long they are kept. |
--segment-store dir:PATH--segment-store s3://bucket[/prefix]?endpoint=... | Store sealed stream segments on a filesystem path or an S3-compatible bucket instead of replicating them across the cluster. |
--backup-store cluster|fs:PATH|s3://bucket[/prefix]?endpoint=... | Where on-demand backups and point-in-time-recovery data live: replicated across the cluster (default), a filesystem path, or an S3-compatible bucket — the actual disaster-recovery option, since it survives a total cluster loss. |
--s3-credentials PATH--allow-insecure-s3 | Credentials for an s3:// store above: a JSON file whose source is static (default — access-key-id plus a secret read from a file or environment variable), env (AWS_* variables), web_identity (STS AssumeRoleWithWebIdentity with a rotating token file, as on EKS), container (ECS / EKS Pod Identity) or imds (EC2 instance profile) — temporary credentials are refreshed before they expire; or the ANIMUS_S3_ACCESS_KEY_ID/ANIMUS_S3_SECRET_ACCESS_KEY environment variables for static keys. --allow-insecure-s3 is the explicit opt-in a plaintext, non-loopback S3 endpoint needs. Add &path_style=false to the store URI for virtual-hosted addressing. Objects over 64 MiB are uploaded as S3 multipart uploads (16 MiB parts); add a bucket lifecycle rule AbortIncompleteMultipartUpload so an upload interrupted by a crash does not linger. |
Run animusd with no arguments for the full usage summary, and animus for the CLI's.
Data directory
Each node writes everything it owns under --dir: a write-ahead log and SSTables
per hosted tablet, control-plane state, and snapshots. Nodes never share a directory —
give each process its own, on storage you would be happy to lose a machine's worth of.
Two supported paths, both tested in simulation: stop every node, install the new
version, and start them all again; or a node-by-node rolling upgrade (one release to
the next only), where animus cluster roll plan gives the order, you restart
one node at a time, animus cluster roll wait blocks until it is healthy,
and the irreversible animus cluster finalize comes last. The CLI restarts
nothing itself. On an operator-managed cluster of at least three nodes, editing
spec.image performs the same roll for you, one pod at a time behind the health
gate, and finalize stays yours unless you set spec.upgrade.finalize: Auto.
Rolling from an older release with multi-item transactions in use carries that release's own abort bug (open). There is no rollback
once a node has run the new binary. Procedure: docs/runbook/upgrade.md.
Troubleshooting
The build fails on an old toolchain
The workspace pins Rust 1.96. Run rustup update and build from the repository root so the pin applies.
A write returns "not leader"
Nodes forward to the tablet's leader and retry through an election, so this should be
transient. If it persists, check animus admin health — a tablet with no reachable
quorum cannot accept writes, by design.
Address already in use
Every node needs its own block of six ports. In a generated config the blocks are contiguous, so two nodes on one host must be given different base ports.
The AWS SDK rejects the endpoint
Set a region and placeholder credentials — the SDKs require them even though AnimusDB
doesn't check them unless SigV4 verification is switched on — and use http://,
not https://, unless you've explicitly configured TLS on the DynamoDB port
(off by default — see the architecture page's ports section).
Something is wrong and it looks like a bug
Please open an issue. Include the
animusd invocation and, if a test failed, the seed it printed — that alone is
enough to replay the exact failure.
Next: how it actually works.
Tablets, the two planes, the consistency model, and the full API surface.