Pre-1.0 hoardDB is pre-1.0. Expect breaking changes.
Configuration
All configuration is done via environment variables or a YAML config file. Environment variables take precedence.
Environment Variables
Core
| Variable | Default | Description |
|---|---|---|
HOARDB_TCP_LISTEN | 0.0.0.0:7433 | TCP+TLS driver listener address |
HOARDB_DATA_DIR | ./data | Data directory, relative to the working directory. The Docker image pins /data via ENV HOARDB_DATA_DIR=/data. |
HOARDB_NODE_ID | hostname | Unique node identifier |
HOARDB_SEED_NODES | (none) | Comma-separated host:port list for cluster formation |
TLS
| Variable | Default | Description |
|---|---|---|
HOARDB_TLS_MODE | auto | auto (self-signed) or custom (user-provided certs) |
HOARDB_TLS_CERT | (none) | Path to TLS certificate (custom mode only) |
HOARDB_TLS_KEY | (none) | Path to TLS private key (custom mode only) |
HOARDB_TLS_CA | (none) | Path to CA certificate for client verification (custom mode only) |
Authentication
| Variable | Default | Description |
|---|---|---|
HOARDB_ROOT_USER | admin | Root username for first start. |
HOARDB_ROOT_PASSWORD | generated on first start | Root password. If unset, the server generates one, writes it to <HOARDB_DATA_DIR>/root.password at 0600 and prints it once at first start; the CLI reads that file automatically when no credential is supplied. Set it explicitly in production — the environment value is wiped after use. |
Replication
| Variable | Default | Description |
|---|---|---|
HOARDB_REPLICATION_FACTOR | 1 | Replication factor (number of nodes holding each bucket); default is majority write concern when > 1 |
HOARDB_SEED_NODES | (none) | Comma-separated seed addresses (host:port). Setting this makes the node a cluster member and enables joining. |
HOARDB_SEED_FINGERPRINTS | (none) | Required whenever HOARDB_SEED_NODES is set. Comma-separated trust anchors: SHA256:<64 hex> (trusted for any seed) or host:port=SHA256:<64 hex> (trusted only for that address). Without it the server refuses to start. |
HOARDB_WRITE_CONCERN | per RF | one, majority or all; default is majority for RF>1, one otherwise |
HOARDB_WAL_RETENTION_INTERVAL | 60 | Seconds between WAL retention passes: each prunes segments every replica has confirmed and every local store has been synced past, so a lagging replica always stays repairable from the log. 0 disables the loop (log grows for the life of the node). |
HOARDB_DISK_HIGH_WATER | 85 | Used-disk percent (0-100) above which this node refuses to accept a new cluster member — joining rebalances data onto it and an mmap-backed write into a full disk crashes the node (SIGBUS). 0 disables the guard. |
HOARDB_AUTO_REBALANCE | true | Whether a membership change immediately moves data so every bucket’s ring owner and replicas hold a copy. false makes the operator drive rebalancing explicitly (migrate per bucket); migration is idempotent. |
Write concern and retention apply to replicated (multi-node) deployments; a single node serves with no WAL and no replication. See replication.md.
A cluster with exactly two members can permanently lose the ability to change membership at all if either node dies (a majority of 2 is 2) — this is logged loudly at startup, but there is no automatic recovery from it. Use an odd member count of three or more in production.
Seed pinning (required for every join)
hoardDB uses self-signed certificates, so a node cannot learn a seed’s identity
from a CA. Every join must be told what it is connecting to: seed_fingerprints
(HOARDB_SEED_FINGERPRINTS) lists the expected certificates. It is required
whenever seed nodes are configured — a server with seeds and no fingerprints
refuses to start, with an error naming the variable. Without an anchor, any host
could impersonate the cluster and read the cluster token off the internode
stream.
Two forms are accepted in the comma-separated list:
SHA256:<64 hex>— trusted for any seed address.host:port=SHA256:<64 hex>— trusted only for that address. The mapped form is recommended because a bare fingerprint would also be accepted if the same key ever appeared at another seed’s address.
Every node prints its fingerprint at startup; the log line carries a
fingerprint field:
grep fingerprint <seed startup log>
A peer verified once is remembered in {data_dir}/cluster/known_servers (keyed
by its internode address), so later dials do not need the configured list. A
certificate that changes for an already-known peer is a hard failure, never a
silent re-trust. Legitimate rotation is explicit:
- Update
seed_fingerprintswith the newhost:port=SHA256:<hex>value (or remove that peer’s line from{data_dir}/cluster/known_serverson the dialing node), then restart the node. - The mismatch error names both fingerprints and both possible causes, so a real rotation is never “fixed” by turning the check off.
A new node added after the cluster is running must also be pinned by the nodes
that dial it; because its key is generated on first start, add its
host:port=SHA256:<hex> entry to the other nodes’ seed_fingerprints (or trust
its entry in known_servers) before adding it. A single node with no seed nodes
never dials a peer and is unaffected.
Storage (BadgerDB tuning)
| Variable | Default | Description |
|---|---|---|
HOARDB_BADGER_BLOCK_CACHE_MB | 128 | Per-bucket BadgerDB block cache. A separate BadgerDB instance is opened per hash/btree/heap/blob bucket, so this cost multiplies by bucket count — lower it for a deployment with many buckets, raise it for a few very large ones (a bucket whose working set outgrows the cache pays extra disk reads). |
HOARDB_BADGER_INDEX_CACHE_MB | 64 | Per-bucket BadgerDB index cache. Same multiply-by-bucket-count consideration as the block cache. |
HOARDB_BADGER_NUM_COMPACTORS | 4 | Per-bucket BadgerDB background compaction goroutines. Also multiplies by bucket count; lower it for many-bucket deployments to bound background CPU/goroutine growth. |
Secondary indexes (declared on a bucket) open their own BadgerDB instance per indexed field and always use the defaults above, regardless of these settings — an index is normally much smaller than its bucket’s primary data.
Networking
| Variable | Default | Description |
|---|---|---|
HOARDB_MAX_CONNS | 1000 | Max concurrent client connections |
Backup and restore
| Variable | Default | Description |
|---|---|---|
HOARDB_DUMP_PIN_CEILING_SECONDS | 300 | Maximum time a dump of a FIFO/LIFO bucket may hold that bucket’s compaction pinned before the dump fails with a named error instead of pinning indefinitely. See Backup and restore. |
Logging
| Variable | Default | Description |
|---|---|---|
HOARDB_LOG_LEVEL | info | debug, info, warn, error |
HOARDB_LOG_FORMAT | json | json or text |
HOARDB_LOG_OUTPUT | stdout | stdout, stderr, or file path |
Metrics
| Variable | Default | Description |
|---|---|---|
HOARDB_METRICS_ENABLED | false | Enable Prometheus /metrics endpoint |
HOARDB_METRICS_LISTEN | 0.0.0.0:9090 | Metrics HTTP listen address |
Audit
| Variable | Default | Description |
|---|---|---|
HOARDB_AUDIT_ENABLED | false | Enable audit logging |
HOARDB_AUDIT_OUTPUT | /var/log/hoarddb/audit.jsonl | Audit log file path |
Debug
| Variable | Default | Description |
|---|---|---|
HOARDB_PPROF_ENABLED | false | Enable pprof endpoint on :6060 |
YAML Config File
For non-containerized deployments, use a YAML config file:
# /etc/hoarddb/config.yaml
server:
listen: "0.0.0.0:4433"
node_id: "node1"
max_conns: 1000
replication_factor: 3
storage:
data_dir: "/var/lib/hoarddb"
hash_ring:
seed_nodes:
- "node1:4433"
- "node2:4433"
- "node3:4433"
# Required when seed_nodes is set. Mapped form is recommended.
seed_fingerprints:
- "node1:4433=SHA256:<64 hex>"
- "node2:4433=SHA256:<64 hex>"
- "node3:4433=SHA256:<64 hex>"
tls:
mode: auto # "auto" or "custom"
cert: /etc/hoarddb/server.crt # custom mode only
key: /etc/hoarddb/server.key # custom mode only
ca: /etc/hoarddb/ca.crt # optional, custom mode only
auth:
argon2:
memory: 65536 # 64 MB
iterations: 3
parallelism: 4
salt_length: 16 # bytes
hash_length: 32 # bytes
logging:
level: info # debug, info, warn, error
format: json # json or text
output: stdout # stdout, stderr, or file path
max_size_mb: 100 # log rotation
max_backups: 7 # days to keep
compress: true
metrics:
enabled: true
listen: "0.0.0.0:9090"
path: "/metrics"
audit:
enabled: true
output: "/var/log/hoarddb/audit.jsonl"
max_size_mb: 500
max_backups: 90
compress: true
log_data_operations: false
store:
fifo:
segment_size: 67108864 # 64 MB
sync_writes: true
lifo:
segment_size: 67108864
sync_writes: true
bucket_limits:
l2_soft_limit: 10000 # warning logged
l2_hard_limit: 100000 # write rejected
TLS Modes
Auto Mode (Default)
The server generates an Ed25519 keypair and self-signed certificate on first start:
{data_dir}/keys/server.key
{data_dir}/keys/server.crt
Clients use trust-on-first-use (TOFU) — fingerprint is pinned on first connect. Identical to SSH.
Custom Mode
Provide your own certificates for production environments with existing CA infrastructure:
export HOARDB_TLS_MODE=custom
export HOARDB_TLS_CERT=/path/to/server.crt
export HOARDB_TLS_KEY=/path/to/server.key
export HOARDB_TLS_CA=/path/to/ca.crt # optional, for client verification
In custom mode, standard TLS certificate verification applies. No fingerprint pinning.
Transport Details
- Protocol: TCP+TLS 1.3 (mandatory, no downgrade)
- Cipher: ChaCha20-Poly1305 (preferred) or AES-256-GCM
- Key Exchange: X25519
- Keys: Ed25519 (faster than RSA, smaller keys)
Replication Factor
The replication factor is node-wide. It is set once, before the server starts, and it applies to every bucket that node holds:
export HOARDB_REPLICATION_FACTOR=3
or in the YAML file, as server.replication_factor. Every node in a cluster
must be given the same value — the factor decides how many nodes hold each
bucket, and nodes that disagree about that disagree about where the data lives.
| RF | Behavior |
|---|---|
| 1 | No replication (default, dev only) |
| 2 | 1 leader + 1 follower |
| 3 | 1 leader + 2 followers (recommended for production) |
Changing it takes a restart, and it changes placement for every bucket at once.
There is no per-database replication factor. create database MyApp with replication_factor 3; is refused, and it used to be worse: earlier versions
accepted that command, reported success, and discarded the clause, so the
database was created with the node-wide factor while the operator believed a
replica set existed.
create bucket <name> { replication_factor: N } parses, but the server does not
honour it yet either — the bucket is created with the node-wide factor. Until
that lands, HOARDB_REPLICATION_FACTOR is the only setting that changes how
many copies of a bucket exist. See replication.md for what
the factor buys you and what it does not (hoardDB never promotes a node
automatically: losing one needs an operator).
Logging
Levels
| Level | When | Example |
|---|---|---|
debug | Development only | “Parsed command: insert into User” |
info | Normal operations | “Node node1 joined hash ring” |
warn | Degraded but functioning | “Replication lag > 5s on node2” |
error | Requires attention | “Failed to write segment: disk full” |
Format
json— Machine-readable, structured fields (production)text— Human-readable terminal output (development)
Correlation IDs
Every request gets a UUID v7 correlation ID, propagated through all internal calls and included in every log line. Enables tracing a single request across nodes:
Client → node1 (correlation_id: 0192a3...) → node2 (replication)
Metrics
Prometheus endpoint at http://<listen>:9090/metrics (when enabled).
Key Metrics
| Metric | Type | Description |
|---|---|---|
hoarddb_requests_total | counter | Total requests by method/status |
hoarddb_request_duration_seconds | histogram | Request latency by method |
hoarddb_bucket_operations_total | counter | Total operations by type |
Audit Logging
Append-only log of security-relevant operations. Written to a dedicated file, not stdout.
What Gets Audited
- Authentication: login success/failure
- User management: create, alter, drop user; grant/revoke roles
- Schema changes: create/drop database, define/drop bucket
Audit Log Format
{
"time": "2026-09-11T12:00:00Z",
"event": "auth.login",
"user": "nathan",
"source_ip": "192.168.1.100",
"result": "success",
"correlation_id": "0192a3b4-c5d6-7890-abcd-ef1234567890"
}
Audit writes are async and non-blocking. If the queue fills (disk full), entries are dropped with a warning.
Docker-Specific Config
Container Design
- Non-root: Runs as
hoarddb:hoarddb(UID 1000) - Static binary:
CGO_ENABLED=0— no glibc dependency - Data volume: Mount at
/data - Health check: Built-in
hoarddb healthcommand
Environment-Only Config
Containers use env vars exclusively — no YAML file needed:
docker run -v hoarddb-data:/data -p 4433:4433 \
-e HOARDB_NODE_ID=node1 \
-e HOARDB_SEED_NODES="node1:4433,node2:4433,node3:4433" \
-e HOARDB_REPLICATION_FACTOR=3 \
-e HOARDB_ROOT_USER=admin \
-e HOARDB_ROOT_PASSWORD=changeme \
hoarddb
Docker Compose Reference
The included docker-compose.yml defines a 3-node cluster. Key settings:
- Each node gets its own
HOARDB_NODE_IDand named volume - All nodes share the same
HOARDB_SEED_NODESlist - Root credentials only set on node1 (first start)
- TLS auto-generates on all nodes
Prompt Themes
The CLI prompt can be themed from a file. The default is the logo palette;
a theme in ~/.hoarddb/theme.yml overrides it.
version: 1
name: my-theme
styles:
brand: {fg: "#f1efe8", bg: null} # "hoard"
accent: {fg: "#a99cf5", bg: null} # "DB"
tile: {fg: "#f1efe8", bg: "#3c3489"} # the [h] mark, as in the logo
database: {fg: "#6f63d6", bg: null} # [dbname]
error: {fg: "#f07178", bg: null} # the `CLI error:` prefix
prompt_format: "{tile:[}{brand:h}{tile:]}{brand:oard}{accent:DB}{database: [%database]}> "
prompt_format_nodb: "{tile:[}{brand:h}{tile:]}{brand:oard}{accent:DB}> "
The directory is 0700, the file 0644, created only if the directory does not
exist. Precedence, lowest to highest: the built-in default, the file, the
HOARDB_THEME environment variable (a path), and --theme.
--theme plain (or HOARDB_THEME=plain) disables colour entirely; docs and
screenshots pin it so output does not depend on whose home directory ran it.
The format grammar: %database is the current database (empty when none is
selected), {style:text} spans colour their text, %%, {{ and }} are the
escaped literals, and a span whose expansion is empty is omitted — which keeps
the [ ] around an absent database from appearing.
Colour is on only when all of these hold: stdout is a terminal, NO_COLOR
is unset, and TERM is not dumb. A piped session therefore carries no escape
bytes. A broken theme file — invalid YAML, a typo in a key, a colour that is
not #rrggbb, an unknown style name, an unclosed brace — prints one warning
naming the file and falls back to the default theme; it never stops the REPL.
The terminal’s default background is queried once per session (OSC 11) so the
tile’s two colours swap for light terminals; HOARDB_BACKGROUND=dark|light
pins the answer for terminals that never reply. Run theme in the REPL to see
the resolved name, file, colour state and background.
Source: docs/user/configuration.md in the repository.