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

VariableDefaultDescription
HOARDB_TCP_LISTEN0.0.0.0:7433TCP+TLS driver listener address
HOARDB_DATA_DIR./dataData directory, relative to the working directory. The Docker image pins /data via ENV HOARDB_DATA_DIR=/data.
HOARDB_NODE_IDhostnameUnique node identifier
HOARDB_SEED_NODES(none)Comma-separated host:port list for cluster formation

TLS

VariableDefaultDescription
HOARDB_TLS_MODEautoauto (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

VariableDefaultDescription
HOARDB_ROOT_USERadminRoot username for first start.
HOARDB_ROOT_PASSWORDgenerated on first startRoot 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

VariableDefaultDescription
HOARDB_REPLICATION_FACTOR1Replication 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_CONCERNper RFone, majority or all; default is majority for RF>1, one otherwise
HOARDB_WAL_RETENTION_INTERVAL60Seconds 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_WATER85Used-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_REBALANCEtrueWhether 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:

  1. Update seed_fingerprints with the new host:port=SHA256:<hex> value (or remove that peer’s line from {data_dir}/cluster/known_servers on the dialing node), then restart the node.
  2. 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)

VariableDefaultDescription
HOARDB_BADGER_BLOCK_CACHE_MB128Per-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_MB64Per-bucket BadgerDB index cache. Same multiply-by-bucket-count consideration as the block cache.
HOARDB_BADGER_NUM_COMPACTORS4Per-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

VariableDefaultDescription
HOARDB_MAX_CONNS1000Max concurrent client connections

Backup and restore

VariableDefaultDescription
HOARDB_DUMP_PIN_CEILING_SECONDS300Maximum 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

VariableDefaultDescription
HOARDB_LOG_LEVELinfodebug, info, warn, error
HOARDB_LOG_FORMATjsonjson or text
HOARDB_LOG_OUTPUTstdoutstdout, stderr, or file path

Metrics

VariableDefaultDescription
HOARDB_METRICS_ENABLEDfalseEnable Prometheus /metrics endpoint
HOARDB_METRICS_LISTEN0.0.0.0:9090Metrics HTTP listen address

Audit

VariableDefaultDescription
HOARDB_AUDIT_ENABLEDfalseEnable audit logging
HOARDB_AUDIT_OUTPUT/var/log/hoarddb/audit.jsonlAudit log file path

Debug

VariableDefaultDescription
HOARDB_PPROF_ENABLEDfalseEnable 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.

RFBehavior
1No replication (default, dev only)
21 leader + 1 follower
31 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

LevelWhenExample
debugDevelopment only“Parsed command: insert into User”
infoNormal operations“Node node1 joined hash ring”
warnDegraded but functioning“Replication lag > 5s on node2”
errorRequires 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

MetricTypeDescription
hoarddb_requests_totalcounterTotal requests by method/status
hoarddb_request_duration_secondshistogramRequest latency by method
hoarddb_bucket_operations_totalcounterTotal 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 health command

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_ID and named volume
  • All nodes share the same HOARDB_SEED_NODES list
  • 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.