esops is a single-binary operations CLI for self-managed Elasticsearch and OpenSearch clusters. The name says it: “es” for Elasticsearch (and its fork OpenSearch), “ops” for operations — when the clusters need work, it’s time to go esops.

It’s built for SRE and DevOps work: the day-to-day maintenance and the high-stakes migrations that pile up around any long-lived cluster. Instead of collecting a folder of curl | jq one-liners, you get one tool that does the job safely.

What it is

A statically linked Go binary — no JVM, no Python runtime, no Docker. It drops straight into an Alpine or scratch image and runs anywhere.

The CLI surface is identical whether you point it at Elasticsearch or OpenSearch. The two are not pretended to be the same product — the tool branches internally where it has to (ILM vs ISM, security models, snapshot keys, reindex dialects) — but you type the same commands either way. It probes the cluster first to find out what it’s actually talking to, rather than trusting your config.

What it can do

  • Cluster operations — health, node and shard overviews, allocation explain, hot threads, security audit, rebalance helper, node drain/uncordon, cluster settings, and a redacted diagnostic bundle for support tickets.
  • Migrations — pre-flight checks, a mapping plan with incompatibility diffs, task-based reindex with resume and adaptive throttling, doc-count and sampled-checksum verification, and atomic alias-swap cutover. Cross-product migrations (Elasticsearch ↔︎ OpenSearch) are first-class.
  • Index management — list, optimize, rollover, shrink, open/close, aliases, settings, templates, and lifecycle policies (ILM on Elasticsearch, ISM on OpenSearch).
  • Snapshots — list, create, verify, restore, and retention pruning, plus repository management.
  • Pipelines — list and inspect ingest pipelines.

Built to be safe in production

  • Named contexts in kubectl style, each with a protection tier. Destructive operations against a prod context require an explicit --i-know-this-is-prod acknowledgement. Read-only commands and dry-runs are always allowed.
  • Blast-radius limits. Bulk destructive operations refuse to run when their scope is too wide unless you opt in explicitly — no accidental “delete everything matching *”.
  • Secrets stay secret. Passwords, API keys, and tokens are loaded by indirection (${env:...}, ${file:...}, ${keyring:...}). Inline plaintext secrets are refused, and auth headers are redacted from logs.
  • Scriptable output. Table (default), JSON, NDJSON, or YAML — all with stable schemas and documented, stable exit codes. NDJSON streams large result sets without buffering. Stdout is pure data; logs and progress go to stderr.
  • Pipeline-friendly. Setting CI=true auto-enables non-interactive mode and JSON logging, so it behaves in automation without per-script flags.

A few examples

Check cluster health, with the wide view:

$ esops ops health -o wide
NAME               STATUS  DIALECT        VERSION  NODES  DATA_NODES  SHARDS  PRIMARIES  UNASSIGNED  ACTIVE%
esops-prod-eu      green   elasticsearch  9.3.3    6      3           120     60         0           100.0

List only the indices that aren’t healthy:

$ esops index list --health yellow -o wide
NAME                  HEALTH  STATUS  PRI  REP  DOCS  STORE  CREATED
esops-stuck-replicas  yellow  open    1    3    1     4.2kb  2026-05-07T08:20:57Z

A zero-downtime reindex, end to end:

# Estimate size/duration and flag incompatible mappings
esops migrate plan --from idx-v1 --to idx-v2

# Task-based reindex that throttles itself when the cluster gets busy
esops migrate reindex --from idx-v1 --to idx-v2 --auto-throttle

# Confirm doc counts and sampled checksums match
esops migrate verify --source idx-v1 --target idx-v2

# Atomic alias swap to the new index
esops migrate cutover --alias idx --from idx-v1 --to idx-v2

Collect a redacted diagnostic bundle for a support ticket:

esops ops diag
# -> ./esops-diag-<cluster>-<timestamp>.tar.gz  (credentials stripped)

Configuration

Contexts work much like kubectl. Drop a config file at ~/.config/esops/config.yaml:

current-context: prod-eu

defaults:
  output: table
  timeout: 30s

contexts:
  prod-eu:
    url: https://es-prod-eu.example.com:9200
    protection: prod
    username: esops
    password: ${keyring:esops/prod-eu}
  dev:
    url: http://localhost:9200
    protection: none

Switch between them on the fly:

esops config get-contexts
esops config use-context dev
esops ops health --context prod-eu

Authentication covers basic auth, API keys, bearer tokens, mTLS client certificates, and AWS SigV4 (for OpenSearch behind IAM).

In short

If you operate Elasticsearch or OpenSearch and keep reaching for curl and jq, esops replaces that pile of incantations with one binary that’s safe to run against a live production cluster — and speaks the same language whether you’re on Elasticsearch or OpenSearch.