Configuration

Ursula is configured by a config file, an optional resource preset, and a small set of CLI overrides.

built-in defaults < preset defaults < config file < CLI overrides

The normal startup command is:

ursula --config /etc/ursula/ursula.toml --node-id 1

For local development without a config file, ursula --preset default starts a single-node in-memory runtime.

CLI overrides

FlagDefaultNotes
--config FILEsearches ./ursula.toml, ./ursula.json, ./ursula.yaml, ./ursula.yml, /etc/ursula/ursula.toml, /etc/ursula/ursula.json, /etc/ursula/ursula.yaml, /etc/ursula/ursula.yml, then user config pathsTOML, JSON, or YAML config file
--preset PRESETdefault when no config file is found; otherwise noneResource preset: default, dev, tiny, small, standard, or large
--node-id IDconfig file or presetOverrides raft.node_id; Helm derives this from the StatefulSet ordinal

CLI --node-id intentionally overrides any raft.node_id value in the config file so the same file can be mounted on every node.

Config file overview

Config keyDefaultPurpose
server.listen127.0.0.1:4437Client/API listener. When server.cluster_listen is unset, this also serves Raft/cluster routes and peer URLs should point here
server.cluster_listennoneOptional separate Raft/cluster listener. When set, server.listen is client/API-only and peer URLs must point here
runtime.core_countavailable_parallelismWorker threads, each pinned to its mailbox event loop
raft.group_countcore_count × 16Total Raft groups. Higher values improve stream-to-group hash spread
raft.wal.backendmemorymemory or disk
raft.wal.pathnoneRequired for raft.wal.backend = "disk"; Ursula stores Raft logs under PATH/raft-log
raft.peersemptyStatic gRPC peer list. Each url is also used for HTTP leader redirects, so clients/gateway must be able to reach it unless future separate redirect URL config exists
raft.init_membership_per_groupfalseOne-time per-group membership bootstrap for a fresh cluster

Without server.cluster_listen, set each [[raft.peers]].url to the node's server.listen address. With server.cluster_listen, set each [[raft.peers]].url to the node's cluster listener address and port. The same peer URL is currently also used for HTTP leader redirects, so that address must be reachable by clients or the gateway as well as other Ursula peers unless separate redirect URL configuration is added later.

Static cluster config

Configure static Raft peers in the same TOML file on every host. Set a different raft.node_id on each host, or keep raft.node_id in the file and override it with --node-id.

[raft]
node_id = 1
group_count = 64
init_membership_per_group = true

[raft.wal]
backend = "disk"
path = "/var/lib/ursula"

[[raft.peers]]
node_id = 1
url = "http://10.0.0.1:4437"

[[raft.peers]]
node_id = 2
url = "http://10.0.0.2:4437"

[[raft.peers]]
node_id = 3
url = "http://10.0.0.3:4437"

Resource presets

--preset supplies a base configuration before the config file is merged. default/dev keep the lightweight single-node development runtime; tiny, small, standard, and large size the Raft runtime and cold-storage knobs for progressively larger nodes. Config-file values override preset values.

PresetIntended useMain defaults
default, devLocal developmentSingle-node in-memory runtime
tinyMemory-bound tests or very small nodes64 groups, 64 MiB cold cache, 4 MiB cold flushes, 2 cold writes, 8 MiB hot/admission caps, 64 MiB HTTP in-flight body budget
smallSmall nodes or cost-sensitive tests128 groups, 64 MiB cold cache, 4 MiB cold flushes, 2 cold writes, 16 MiB hot/admission caps, 64 MiB HTTP in-flight body budget
standardProduction baseline256 groups, 256 MiB cold cache, 8 MiB cold flushes, 4 cold writes, 64 MiB hot/admission caps, 256 MiB HTTP in-flight body budget
largeLarger nodes with more cache and write headroom512 groups, 512 MiB cold cache, 16 MiB cold flushes, 8 cold writes, 128 MiB hot/admission caps, 512 MiB HTTP in-flight body budget

Config-file cold storage and tuning

Cold storage is configured under [storage.cold] and [storage.cold.s3] in the config file. The deployment tools (Helm and scripts/ursula_ec2.py) map their deployment-specific inputs into these canonical keys.

Config keyDefaultPurpose
storage.cold.backendnonenone, memory, or s3.
storage.cold.s3.bucketnoneRequired when backend = "s3".
storage.cold.flush_interval1sBackground flush worker interval.
storage.cold.flush_size8MiBTarget bytes flushed per group per pass.
storage.cold.flush_max_concurrency4Parallel cold writes.
storage.cold.max_hot_size_per_group64MiBPer-group hot ceiling; explicit 0 disables the cap, and omitted inherits default/preset behavior.
runtime.live_read_max_waiters_per_core65536SSE waiter cap per core; 0 disables the limit.
observability.tokio_consolefalseEnables Tokio console when the binary is built with the feature.

Minimal examples

In-memory single node:

ursula --preset default

Disk-backed single node:

[server]
listen = "127.0.0.1:4437"

[runtime]
core_count = 4

[raft]
node_id = 1
group_count = 64

[raft.wal]
backend = "disk"
path = "./data"
ursula --config ./ursula.toml

Three-node durable cluster (same file on each host, override raft.node_id per host):

[server]
listen = "0.0.0.0:4437"

[runtime]
core_count = 16

[raft]
node_id = 1
group_count = 256
init_membership_per_group = true

[raft.wal]
backend = "disk"
path = "/var/lib/ursula"

[storage.cold]
backend = "s3"
root = "ursula-prod-20260518"

[storage.cold.s3]
bucket = "my-ursula-bucket"
region = "us-east-1"

[[raft.peers]]
node_id = 1
url = "http://10.0.0.1:4437"

[[raft.peers]]
node_id = 2
url = "http://10.0.0.2:4437"

[[raft.peers]]
node_id = 3
url = "http://10.0.0.3:4437"
ursula --config /etc/ursula/ursula.toml --node-id 1

See Deploy a cluster for the full walk-through and Configure S3 for cold-tier specifics.