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
| Flag | Default | Notes |
|---|---|---|
--config FILE | searches ./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 paths | TOML, JSON, or YAML config file |
--preset PRESET | default when no config file is found; otherwise none | Resource preset: default, dev, tiny, small, standard, or large |
--node-id ID | config file or preset | Overrides 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 key | Default | Purpose |
|---|---|---|
server.listen | 127.0.0.1:4437 | Client/API listener. When server.cluster_listen is unset, this also serves Raft/cluster routes and peer URLs should point here |
server.cluster_listen | none | Optional separate Raft/cluster listener. When set, server.listen is client/API-only and peer URLs must point here |
runtime.core_count | available_parallelism | Worker threads, each pinned to its mailbox event loop |
raft.group_count | core_count × 16 | Total Raft groups. Higher values improve stream-to-group hash spread |
raft.wal.backend | memory | memory or disk |
raft.wal.path | none | Required for raft.wal.backend = "disk"; Ursula stores Raft logs under PATH/raft-log |
raft.peers | empty | Static 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_group | false | One-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.
| Preset | Intended use | Main defaults |
|---|---|---|
default, dev | Local development | Single-node in-memory runtime |
tiny | Memory-bound tests or very small nodes | 64 groups, 64 MiB cold cache, 4 MiB cold flushes, 2 cold writes, 8 MiB hot/admission caps, 64 MiB HTTP in-flight body budget |
small | Small nodes or cost-sensitive tests | 128 groups, 64 MiB cold cache, 4 MiB cold flushes, 2 cold writes, 16 MiB hot/admission caps, 64 MiB HTTP in-flight body budget |
standard | Production baseline | 256 groups, 256 MiB cold cache, 8 MiB cold flushes, 4 cold writes, 64 MiB hot/admission caps, 256 MiB HTTP in-flight body budget |
large | Larger nodes with more cache and write headroom | 512 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 key | Default | Purpose |
|---|---|---|
storage.cold.backend | none | none, memory, or s3. |
storage.cold.s3.bucket | none | Required when backend = "s3". |
storage.cold.flush_interval | 1s | Background flush worker interval. |
storage.cold.flush_size | 8MiB | Target bytes flushed per group per pass. |
storage.cold.flush_max_concurrency | 4 | Parallel cold writes. |
storage.cold.max_hot_size_per_group | 64MiB | Per-group hot ceiling; explicit 0 disables the cap, and omitted inherits default/preset behavior. |
runtime.live_read_max_waiters_per_core | 65536 | SSE waiter cap per core; 0 disables the limit. |
observability.tokio_console | false | Enables 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.