README.md

August 28, 2026 · View on GitHub

Etcd-walker

Interactive TUI for browsing and managing etcd v2 / v3 datastores

etcd-walker is a terminal application that lets you navigate an etcd key-value store as if it were a filesystem. You can browse, create, edit, rename, delete and export keys and directories from a single curses-style interface, with full support for etcd v2 (HTTP) and etcd v3 (gRPC) — including authentication and TLS.

Profiles

Grab the latest pre-built binaries / .deb packages here: latest version


Features

  • File-explorer style navigation of etcd keys/directories
  • Create / read / update / delete keys and directories
  • Rename keys and directories (including recursive directory rename)
  • Set or clear a key's TTL / expiry (Ctrl+T), entered as seconds or a duration like 1h30m; remaining TTL is shown live in the details pane (v3 via leases, v2 via native key TTL). Editing a key's value preserves its TTL instead of dropping it.
  • Revision history for keys on etcd v3 (Ctrl+V): browse a key's stored MVCC revisions newest-first, inspect any old value (JSON pretty-print / hex for binary), see a colored line-diff against the current value, and restore an old value in one keystroke — the key's live TTL/lease is preserved. How far back it reaches depends on the cluster's compaction policy; v2 has no value history.
  • Time travel (Ctrl+G, v3): pin a pane to a past etcd revision and browse the whole keyspace as it was at that moment — listings, values, details, recursive find and export all read at that revision. Enter an absolute revision, -N for N revisions back, or nothing to return to live. A pinned pane is read-only (the value editor opens as a viewer) and says so in its title and details pane; keys deleted since are visible again. Bounded by the cluster's compaction point, which is reported plainly instead of showing an empty tree
  • Two panes (Ctrl+B / -dual): a second browser pane side by side, Tab to switch. Each pane keeps its own directory, cursor and revision — so you can put yesterday's revision next to today's tree — and F5/F6 copy or move the selected entry into the other pane's directory without asking for a path
  • Quick search inside the current level (/ or Ctrl+S)
  • Recursive find under the current directory (Ctrl+F) — case-insensitive substring match on key paths, optionally inside values, with a results picker that jumps straight to the match
  • Duplicate a key or a whole directory subtree to a new path (Ctrl+D), TTLs/leases included; the source is left untouched
  • Jump to an absolute or relative path (Ctrl+J)
  • Multi-line editor for large key values (Ctrl+E)
  • Details pane with size/lines/SHA-256, live TTL, etcd revisions (create/mod revision and version on v3), pretty-printed JSON preview and an xxd-style hex preview for binary values
  • Listings are keys-only on v3 (values are fetched on focus), so browsing huge trees stays light
  • Export the current directory to JSON (Ctrl+W)
  • Import keys from a JSON file via a built-in filesystem browser, with overwrite / skip-existing modes (Ctrl+O)
  • Copy a key path or value to the system clipboard, with OSC52 fallback for SSH / tmux sessions (Ctrl+P, Ctrl+Y)
  • Etcd v2 and v3 support, plus an auto mode that probes v3 first and falls back to v2
  • Authentication (etcd v3, username + password)
  • Full TLS / mTLS support (CA, client cert/key, optional skip-verify)
  • Hidden / underscore-prefixed key support, highlighted in yellow
  • Read-only sessions (-read-only): every mutating action is refused with an explanation and the header turns yellow with [READ-ONLY]. The value editor still opens, disabled, so you can read a value in full
  • Protected prefixes (-protect /registry): writes on or under a protected path require the prefix's basename to be typed out first. Covers every write route — create, delete, rename, TTL, save, copy, import and revision-restore — including bulk imports where a single protected target gates the whole batch
  • Session journal (Ctrl+A): every change the session made, listed in order and exportable as an etcdctl script (or copied to the clipboard). Operations etcdctl cannot reproduce from what was recorded — renames, copies, TTLs — are emitted as clearly-marked comments rather than as commands that would do the wrong thing
  • Dry-run mode (-dry-run): changes are recorded in the journal but never sent to the cluster. Rehearse against production, review the script, then run it deliberately
  • Deleting a directory requires typing its name — a recursive delete is a single unrecoverable range delete, not something a stray Enter should do
  • Undo snapshots (Ctrl+U): before every recursive directory delete the subtree is exported to a local file under $XDG_STATE_HOME/etcd-walker/ snapshots, and Ctrl+U lists them newest-first to restore or discard. A snapshot that cannot be written cancels the delete rather than leaving you without an undo (-no-snapshot opts out). Restores go through the same policy guard and journal as any other bulk write, and binary values survive byte for byte
  • Failed refreshes are visible, never silent: when the on-focus re-read of a key fails, the details pane says so and the row is greyed and marked (cached), so a stale value is never mistaken for a live one
  • Optional JSON config file (/etc/etcd-walker/config.json)
  • Configurable per-operation timeout

Hotkeys

KeyAction
EnterEnter directory
BackspaceGo up one directory
Ctrl+NCreate new key or directory
Ctrl+DDuplicate key or directory to a new path
DeleteDelete a key (confirm) or directory (type its name)
Ctrl+EEdit value (multi-line) / rename directory
Ctrl+RRename key or directory
Ctrl+TSet / clear TTL on a key (seconds or 1h30m)
Ctrl+VRevision history of a key (v3): view / diff / restore
Ctrl+GBrowse a past revision (v3): number, -N, or empty for live
Ctrl+BShow / hide the second pane
TabSwitch panes
F5 / F6Copy / move the selected entry to the other pane
Ctrl+UUndo snapshots — restore a deleted directory
Ctrl+S or /Quick search inside the current level
Ctrl+FRecursive find (paths, optionally values)
Ctrl+JJump to absolute or relative path
Ctrl+WExport current directory to a JSON file
Ctrl+OImport keys from a JSON file (file browser)
Ctrl+ASession journal — review / export as etcdctl
Ctrl+PCopy current path to clipboard
Ctrl+YCopy current key value to clipboard
Ctrl+HShow in-app hotkeys help (scrolls: /, PgDn/PgUp, Home/End; Esc or q closes)
Ctrl+QQuit

Configuration

etcd-walker reads its settings from three sources, applied in order (later sources override earlier ones):

  1. Hard-coded defaults
  2. JSON config file/etc/etcd-walker/config.json by default, overridable with -config /path/to/file.json. The file is optional; if it does not exist etcd-walker silently uses defaults.
  3. Command-line flags — override individual fields from the config file.

Config file (JSON)

Default location: /etc/etcd-walker/config.json

Full schema (every field is optional):

{
  "host": "127.0.0.1",
  "port": "2379",
  "protocol": "v3",
  "debug": false,

  "username": "root",
  "password": "supersecretpassword",

  "tls_enabled": false,
  "tls_ca_file":   "/etc/etcd-walker/ca.crt",
  "tls_cert_file": "/etc/etcd-walker/client.crt",
  "tls_key_file":  "/etc/etcd-walker/client.key",
  "tls_skip_verify": false,

  "timeout_seconds": 5,

  "read_only": false,
  "dry_run": false,
  "protected_prefixes": ["/registry"],

  "snapshot_before_delete": true,
  "dual_pane": false
}

Field reference:

FieldTypeDefaultNotes
hoststring127.0.0.1etcd host
portstring2379etcd port
protocolstringautov2, v3, or auto (try v3 then fall back to v2)
debugboolfalseEnable debug-level logging on stderr
usernamestringemptyetcd auth username (gRPC auth on v3, basic auth on v2)
passwordstringemptyetcd auth password
tls_enabledboolfalseUse HTTPS / TLS for etcd v3
tls_ca_filestringemptyCA cert for verifying the server
tls_cert_filestringemptyClient certificate for mutual TLS
tls_key_filestringemptyClient private key for mutual TLS
tls_skip_verifyboolfalseSkip server cert validation (insecure)
timeout_secondsint5Per-operation timeout against etcd (0 → 5)
read_onlyboolfalseRefuse every mutating action for the session
dry_runboolfalseRecord changes in the journal without performing them
protected_prefixes[]stringemptyPrefixes needing a typed confirmation before any write; "/" protects everything
snapshot_before_deletebooltrueExport a directory's subtree to a local undo snapshot before deleting it. Omitting the key keeps it on
dual_paneboolfalseStart with two panes side by side

Command-line flags

-config string             path to JSON config file (default "/etc/etcd-walker/config.json")
-host string               etcd host (e.g. 127.0.0.1)
-port string               etcd port (e.g. 2379)
-protocol string           etcd protocol: v2, v3, auto (default: auto)
-username string           etcd auth username
-password string           etcd auth password (consider using config file)
-tls bool                  enable TLS/HTTPS for etcd v3
-tls-ca string             path to CA certificate file
-tls-cert string           path to client certificate file (mTLS)
-tls-key string            path to client key file (mTLS)
-tls-skip-verify bool      skip server certificate verification (insecure)
-timeout string            etcd operation timeout in seconds
-read-only bool            refuse every mutating action for this session
-dry-run bool              record changes without performing them
-protect string            comma-separated prefixes needing a typed
                           confirmation before any write (e.g. /registry)
-no-snapshot bool          do not save an undo snapshot before deleting a
                           directory (snapshots are on by default)
-dual bool                 start with two panes side by side
-debug bool                enable debug logging

Flags that are explicitly set on the command line always win over the config file. Flags that are omitted leave the config file value untouched.

Boolean flags take the usual Go forms — -read-only on its own turns the setting on, and -read-only=false turns it off again, which is how you override a "read_only": true in the config file for one session. -protect replaces the configured protected_prefixes rather than adding to them, so a session's protection can always be read off its command line. -no-snapshot is spelled as the negation of a default-on setting, so -no-snapshot=false re-enables undo snapshots over a config file that turned them off.

Browsing production safely
etcd-walker -host prod-etcd -read-only

Nothing can be changed; the header says so. To stay editable everywhere except the paths that would take the cluster down:

etcd-walker -host prod-etcd -protect /registry,/vault

A write anywhere under /registry then asks you to type registry before it goes through — including one buried in a bulk import.

To rehearse a change set before committing to it:

etcd-walker -host prod-etcd -dry-run

Make the edits as usual, press Ctrl+A to review exactly what would have happened, and export it as a script to run once you are satisfied.

Looking at the past

etcd's revision counter is global and monotonic, so "what did this look like before the change?" is a property of the whole keyspace, not of one key. Ctrl+G pins the pane to a revision:

EnteredMeaning
12345the keyspace as of revision 12345
-100a hundred revisions ago
(empty)back to the live cluster

Everything the pane does then reads at that revision — the listing, the details pane, Ctrl+F and Ctrl+W — and keys deleted since are visible again. The title reads [ /dir @ rev 12345 ] and every mutation is refused while it is set, because a snapshot of a moment is not a place to write.

How far back it reaches is set by the cluster's compaction policy. A revision that has been compacted away is reported as exactly that, and the pane stays where it was rather than switching to a view that cannot load.

Two panes

Ctrl+B opens a second pane (or start with -dual); Tab switches between them. Each pane has its own directory, cursor and revision, so a natural layout is yesterday's revision on one side and the live tree on the other.

F5 copies the selected key or directory into the other pane's directory and F6 moves it — no path to type, because the other pane is the destination. Both go through the same policy guard, journal and dry-run as every other write.

Undoing a directory delete

A recursive delete is one range delete on the server with nothing to undo it with. Before performing one, etcd-walker exports the whole subtree to $XDG_STATE_HOME/etcd-walker/snapshots (falling back to ~/.local/state/…; ETCD_WALKER_STATE_DIR overrides it):

20260828T140304-registry_pods.json

Ctrl+U lists those snapshots newest-first with their path, time and key count, and offers to restore one (overwriting or skipping keys that exist again) or delete the file. A restore is an ordinary bulk write: it is policy-gated and lands in the session journal.

If the snapshot cannot be written the delete does not happen — the protection is worth nothing if it silently degrades. Pass -no-snapshot to delete without one.

Configuration examples

Minimal — connect to a local insecure etcd v3:

{ "protocol": "v3" }

Authenticated v3 over plain TCP:

{
  "host": "etcd.internal",
  "port": "2379",
  "protocol": "v3",
  "username": "root",
  "password": "supersecretpassword"
}

Authenticated v3 over mutual TLS:

{
  "host": "etcd.example.com",
  "port": "2379",
  "protocol": "v3",
  "username": "root",
  "password": "supersecretpassword",
  "tls_enabled": true,
  "tls_ca_file":   "/etc/etcd-walker/ca.crt",
  "tls_cert_file": "/etc/etcd-walker/client.crt",
  "tls_key_file":  "/etc/etcd-walker/client.key",
  "timeout_seconds": 10
}

One-shot connection without a config file:

./etcd-walker \
  -host etcd.example.com -port 2379 \
  -protocol v3 -tls \
  -tls-ca /etc/etcd-walker/ca.crt \
  -tls-cert /etc/etcd-walker/client.crt \
  -tls-key  /etc/etcd-walker/client.key \
  -username root -password 'supersecretpassword'

Authentication

Since v0.3.2 etcd-walker supports authentication. Credentials are used by both backends — gRPC authentication on v3, HTTP basic auth on v2 — though TLS is v3 only (the v2 backend always connects over plain HTTP).

The header shows the cluster's auth state as Auth: ON, Auth: OFF, or Auth: ? when it could not be determined. If the server has auth enabled and the credentials are missing or wrong, etcd-walker shows the etcd error in-app so the misconfiguration is easy to spot.

Note: the auth state is probed in every mode; Auth: ? means the server did not report either way.


Limitations

  • The v2 backend always connects over plain HTTP — the TLS options apply to v3 only.
  • No live refresh: the listing updates after an action, not when the cluster changes underneath you. Re-enter a directory to re-read it.
  • Moving the cursor costs a round trip — the details pane re-reads the focused key each time — so scrolling a large directory over a slow link is sluggish. A failed re-read is always reported, never silently cached.
  • Renames and copies are client-side copy-then-delete loops, not transactions: a failure part-way leaves a partial target.
  • Concurrent edits are last-write-wins. Two people editing the same key will not notice each other; the second save overwrites the first.
  • v3 listings, recursive find and export fetch their whole key range in a single request (listings are keys-only, which keeps browsing light, but a find/export over a huge keyspace can be slow and memory-hungry).
  • Clipboard: when only the OSC52 fallback is available, values larger than 10 kB are refused rather than silently truncated.
  • v3 keys containing // or a trailing / are displayed at their normalized path and cannot be opened or edited.
  • Browsing a past revision needs etcd v3 and reaches only as far back as the cluster's compaction point; the v2 store keeps no past revisions at all, so Ctrl+G refuses there rather than showing current data as history.
  • Undo snapshots cover recursive directory deletes only. A single key is recoverable from its own revision history (Ctrl+V) on v3.
  • Snapshots are written to the local disk unencrypted, exactly like an export — do not snapshot secrets onto a machine you would not export them to. They are never pruned automatically; Ctrl+U deletes them.

Testing

make test           # unit suite (hermetic: no network, no terminal)
make test-race      # same, under the race detector
make cover          # total statement coverage

There is also an integration suite that runs against a real etcd — lease sharing, revision history and prefix boundaries are server semantics that a mock cannot prove:

make test-integration

It defaults to 127.0.0.1:2379 and takes ETCD_WALKER_TEST_ENDPOINT, ETCD_WALKER_TEST_USER and ETCD_WALKER_TEST_PASSWORD from the environment. Everything it writes goes under a unique /etcd-walker-it/… prefix and is removed afterwards.

CI runs the unit suite (plus -race), a gofmt/vet check, cross-builds for linux amd64/arm64 and freebsd, and the integration suite against an etcd service container on every push and pull request.


Building

The repository ships a Makefile that wraps the whole cross-build matrix. Run make help to list every target (it prints the resolved VERSION):

make help            # list targets
make x86_64          # linux/amd64 into dist/
make x86_64-static   # fully static linux/amd64 (CGO off)
make uconsole        # linux/arm64 (ClockworkPi uConsole CM4)
make pizero2w        # linux/arm64 (Raspberry Pi Zero 2 W, 64-bit OS)
make all             # every platform into dist/
make debs            # .deb packages (amd64 + i386 + arm64 + armhf)
make test vet fmt    # developer shortcuts

The manual go build invocations below are equivalent if you prefer not to use the Makefile.

Standard build:

go build ./cmd/etcd-walker

Static build (no libc dependency, useful inside scratch / distroless containers):

go build -o etcd-walker_linux_x64_static \
  -ldflags "-linkmode external -extldflags -static" \
  ./cmd/etcd-walker

Verify with ldd:

ldd etcd-walker

32-bit (i686) build:

GOOS=linux GOARCH=386 go build -o etcd-walker_linux_i686 ./cmd/etcd-walker

FreeBSD build:

GOOS=freebsd GOARCH=amd64 go build -o etcd-walker_freebsd_x86_64 ./cmd/etcd-walker

Building a .deb package

Install the build dependencies once:

sudo apt-get install git devscripts build-essential lintian upx-ucl golang

Then build the packages with the Makefile (preferred):

make debs                       # amd64 + i386 + arm64 + armhf
make deb-arm64                  # a single architecture
make debs VERSION=0.6.6         # override the version

The legacy shell helpers still work and produce an equivalent package:

./build-deb.sh           # amd64
./build-deb-arm64.sh     # arm64

The resulting package is written to build/etcd-walker_<version>_<arch>.deb.


Running

./etcd-walker [-config path] [-host host] [-port port] [-protocol v2|v3|auto] \
              [-username user] [-password pass] [-debug] \
              [-tls] [-tls-ca path] [-tls-cert path] [-tls-key path] \
              [-tls-skip-verify] [-timeout seconds]

Default values: host 127.0.0.1, port 2379, protocol auto, debug false, timeout 5s.


Starting etcd for development / testing

Run a throwaway etcd in Docker:

docker run -d --restart unless-stopped -p 2379:2379 --name etcd \
  quay.io/coreos/etcd:v3.3.27 /usr/local/bin/etcd \
  -advertise-client-urls http://0.0.0.0:2379 \
  -listen-client-urls    http://0.0.0.0:2379

Smoke-test from the host:

curl -L http://localhost:2379/v2/keys/test -XPUT -d value="test value"

Then point etcd-walker at it:

./etcd-walker -host 127.0.0.1 -port 2379 -protocol auto

Architecture

For an in-depth look at the project's internal structure, package layout and design decisions, see DESIGN.md. Planned features and the open findings from the latest code review live in ROADMAP.md.