docs · pre-release
Menu
Concepts / The KV store

The KV store

Two files already configure everything shep runs: a Flockfile configures a sheep, shep.toml configures the shepherd and its dogs. Neither has a field for "the port the last on-call engineer picked" or a feature flag a provisioning script wants to leave a note about. The KV store is what's left over: a flat, file-locked junk drawer, explicitly not the primary config path.

note

Reaching for this to configure a sheep? Put it in the Flockfile. Configuring a dog? Put it under[dog.<name>] in shep.toml. This store is only for what's left after those two.

The three verbs

$ shep set bark.cooldown 30s
$ shep get bark.cooldown
$ shep get
$ shep unset bark.cooldown
$ shep unset --all

shep set <key> <value> writes a key, replacing whatever was there. shep get <key> prints one value;shep get with no key lists everything.shep unset <key> removes one key;shep unset --all empties the store: a flag, not a key named all, because nothing stops you having a key calledall and shep unset all would then mean something different depending on your own store.

Real output

Run against a fresh $SHEP_HOME, no shepherd listening:

$ shep set bark.cooldown 30s
KEY            VALUE
bark.cooldown  30s

$ shep set feature.flag on
$ shep get
KEY            VALUE
bark.cooldown  30s
feature.flag   on

$ shep get nope.key
error[not_found]: `nope.key` is not set
$ echo $?
3

$ shep unset bark.cooldown
REMOVED
1
$ shep unset --all
REMOVED
1
$ shep get
KEY  VALUE

A key that was never there answers NotFound on bothget and unset, exit code 3 either way: a script can write shep get feature.flag || echo default and trust the exit code rather than parsing output to tell "empty" from "missing."

The key grammar

A key is [A-Za-z0-9._-], one to 128 bytes, and can't start with a dot. A dot is part of a name, not a path:bark.cooldown is one flat key, not a nested object, andshep get bark won't find it. This is deliberate: a nesting grammar here would be a second config language, with its own quoting rules, for a store that's explicitly not the primary one. The narrow alphabet is also why shep get $key never needs quoting in a script. A value is a string, capped at 4 KiB: the store is read whole on every access, and the cap is what keeps kv.json from quietly becoming a blob store.

No shepherd required

shep set/get/unset never touch the socket: they read and write $SHEP_HOME/kv.jsondirectly, the same way shep enable writesshep.toml with nothing listening. That's what makes this store usable during provisioning, before any shepherd has ever booted on the machine. A dog reads the same store throughshep_core::kv rather than over the wire: a0600 file inside a 0700$SHEP_HOME, opened by a process running as the same user, already has every property a socket round trip would have bought it.

Concurrent writers lose nothing

Two shep set invocations racing (two provisioning scripts, or an operator and a dog) are serialized by an exclusive advisory lock on a sibling kv.json.lock. Whichever loses the race simply waits its turn; neither write is silently dropped. The file itself is written through a BTreeMap, so keys are always in sorted order and two writes of the same content produce byte-identical files: safe to keep in a dotfiles repository,git add and diff like any other small config file. Mode is0600.

Where to go next