ebman
A k9s-style terminal UI for AWS Elastic Beanstalk.
Browse environments, drill into events / instances / metrics / queue / config, stream CloudWatch logs, edit env vars / option settings, deploy new versions — all without leaving the terminal.
Highlights
- Live env table with sort, filter, group-by-app, health sparkline (trend window auto-labelled), severity tints, mouse support.
Tabcycles between Envs and Apps scope. - Drill-down per env — opens to a Health rollup tab (drillable:
j/kwalks items,Enterjumps to source). Other tabs: Events (regex-searchable), Instances (with health causes + embedded SSM shell), Metrics (CloudWatch line charts; custom metrics with arbitrary dimensions), Queue (Worker tier; main + DLQ stats with viewer), Logs (one-shot snapshot or realtail -ffrom CW Logs), Config (tags + env vars read-only, plus cost estimate). - Red-env triage —
:why(or!on the selected env) opens a four-section diagnostic overlay: recent events, alarms, instance health, recent deploys. Worker envs also get a main + DLQ peek. Updating envs are labelled with the kind of update in flight (deploy / config / scale); an env with active CW alarms no longer renders as plain green "Ready". Worker envs with a non-empty DLQ flip Red even when EB calls them healthy. - Daily-driver write surface — env vars (
:env set/unset), tags (:tag/:untag), version deploy from existing label or local zip / S3 (:deploy --from, with--previewfor a side-by-side current-vs-candidate overlay), saved-config CRUD, CloudWatch alarms CRUD, log streaming toggle, notifications endpoint, managed-update window, ALB scheme, instance type, key pair, IAM roles, deployment policy, rolling-update settings, health-check URL, capacity (:capacity— Min / Max / Instance type / Cooldown in one modal), plus a generic:set-option NAMESPACE OPTION VALUEescape hatch. - Worker / SQS workflow — DLQ viewer with per-message resend (
r), strict-typed purge (p), bulk delete (x), peek-and-tail with long-polling. - Multi-region / multi-account —
:region allfans across every configured region in parallel;:account NAMEswitches viasts:AssumeRole(configureaccounts.NAMEinconfig.toml);:accountslists AWS-Organizations child accounts;:find-envand:org-healthboth fan across~/.awsprofiles and configured AssumeRole accounts. - Bulk ops —
spacemulti-selects;:batch-rebuild,:batch-restart,:batch-deploy LABEL,:batch-tag KEY VALUE,:batch-untag KEY,:batch-set-option NAMESPACE OPTION VALUEall fan out in parallel with per-env audit + pending pill rows. - Safety —
--read-onlyCLI flag or:readonly ondisables every write surface; destructive actions (Terminate, DLQ purge) require typed-name confirmation; pre-flight dry-run shows impact (N instances across M AZs) + last 3 events before authorising; recent-change / mid-deploy traffic warnings in the confirm modal. - Audit log — every dispatched action and its outcome are appended to
~/.cache/ebman/audit.log; rotates at 1 MiB. - Power-user ergonomics — fuzzy command palette (
Ctrl-K) across commands / envs / saved views / plugins, named filters + saved views, custom keybindings (F1-F12and uppercase letters via~/.config/ebman/keys.toml), plugin commands (~/.config/ebman/commands.toml), in-app:loglevelreload,:diffbetween envs. - Headless / scriptable —
--control-socket PATHexposes a Unix-socket interface;ebman ctl <op>is a one-shot client (screen/state/key <spec>/cmd <:cmd>). - Non-interactive CLI —
ebman envs [--json]/ebman action rebuild --env NAME/ebman ctl ...for scripts and CI.
Install
Homebrew (macOS / Linux):
Pre-built binary:
Download the tarball for your platform from the GitHub Releases page, verify the *.sha256 next to it, extract, and put ebman on your PATH.
Cargo:
Tested on Rust 1.91+. macOS (Apple Silicon + Intel) and Linux x86_64. AWS SDK uses the standard credentials chain (AWS_PROFILE / AWS_REGION env, ~/.aws/credentials, instance role, etc.).
Fonts (optional, for the prettier glyph set)
Ebman runs fine in any terminal with the default icons = "unicode" config. For the Powerline-style pill chain, tab ribbon, and per-tab MDI icons (icons = "powerline" or icons = "auto"), your terminal needs a Nerd Font installed — vanilla Powerline fonts give you the triangles but tofu/boxes where the tab icons should be.
1. Install a Nerd Font:
2. Set your terminal's font to one of the Mono variants — they're sized for fixed-width TUIs (e.g. MesloLGS Nerd Font Mono, JetBrainsMono Nerd Font Mono):
- iTerm2: Preferences → Profiles → Text → Font
- Terminal.app: Preferences → Profiles → Font → Change
- Ghostty / Alacritty / WezTerm:
font-familyin the relevant config file - VS Code / Cursor terminal:
terminal.integrated.fontFamilyin settings
3. Tell ebman to use the new glyphs — either run :settings in ebman and pick auto (or powerline) from the Icons field, or add this to ~/.config/ebman/config.toml:
= "auto" # probes the terminal at startup; falls back to "unicode"
Restart ebman (or use ebman ctl reload if you're driving via the control socket) so the startup probe runs against your new font. icons = "powerline" skips the probe and forces the Nerd glyph set unconditionally.
Without a Nerd Font, stick to icons = "unicode" (the default) — everything still works, you just don't get the per-tab MDI icons.
Quickstart
Once running, press ? for a per-context keymap (Detail, DLQ, Action menu, Saved-configs overlay all have scoped help).
Keys
Normal mode (env table)
| Key | Action |
|---|---|
j / k / wheel |
Move selection |
g / G |
Top / bottom |
1 – 9 |
Jump to position |
' |
Name-jump (type prefix) |
Enter |
Drill into env |
Tab / Shift-Tab |
Switch scope (Envs ↔ Apps) |
a |
Actions menu |
b |
Open env in AWS console |
D |
Describe overlay (raw env JSON) |
space |
Multi-select |
* |
Pin / unpin |
! |
:why overlay (Red-env diagnostic) |
/ |
Filter |
: |
Command bar |
^K |
Command palette |
s / S |
Cycle sort key / reverse |
^G |
Toggle group-by-application |
^E |
Toggle events panel |
^] |
Cycle focus (table ↔ events panel) |
^D |
Cycle view mode (default / compact / spacious) |
^X |
Toggle redact mode |
^Y |
Yank filtered table as TSV |
^W |
Yank equivalent aws elasticbeanstalk describe-environments |
y / Y |
Yank CNAME / name |
f |
Freeze / unfreeze auto-refresh |
r / p |
Switch region / profile |
^R / F5 |
Force refresh |
? |
Help |
q / ^C |
Quit |
Detail view
Tabs cycle with Tab / Shift-Tab (or l / h). ^R re-fetches the active tab; R toggles per-tab auto-refresh; a opens the env actions menu; b opens the env in the AWS console.
| Tab | Per-tab keys |
|---|---|
| Health (default) | j/k walk items, Enter drill into source tab (Events / Instances / Queue) |
| Events | / filter, n/N next/prev match |
| Instances | Enter / i info overlay, b EC2 console, s embedded SSM shell, y yank id, x terminate (Y/N) |
| Metrics | [ / ] cycle range (15m → 24h), mouse hover for value-at-cursor |
| Queue | j/k Main ↔ DLQ, Enter opens viewer, d quick-open DLQ |
| Logs | ^R one-shot snapshot, s open live CW Logs streaming overlay, / filter |
| Config | scrollable; tags + env vars + cost estimate read-only |
DLQ viewer
j/k move, Enter view body, r resend (DLQ → main), x delete, p purge (typed-name confirm), m toggle Main ↔ DLQ, ^R re-peek.
Command reference
Type : to open the command bar. Tab-completion is not implemented, but Ctrl-K fuzzy-searches every command + env + view + plugin.
Navigation / inspection
:region NAME/:region all— switch region, or fan out across every configured region.:profile NAME— switch AWS profile.:account NAME— switch to a configured AssumeRole account (accounts.NAMEinconfig.toml). Falls back to:profile NAMEaliasing when noaccounts.entry exists.:accounts— list child accounts in the active AWS organization; rows matching a configuredaccounts.NAMEget a:account NAMEswitch hint.:sort KEY [desc]— set sort (name/app/status/health/version/age).:group on|off— toggle group-by-application.:redact on|off— toggle redact mode.:events on|off— toggle events panel.:filter NAME/:f NAME— load a saved filter.:save NAME/:drop NAME/:filters— manage named filters.:save-view NAME/:view NAME/:views/:view-drop NAME— saved views (filter + sort + grouping + scope).:cols list|hide NAME|show NAME|reset— manage columns.:pin— pin / unpin selected env.:alias NAME LABEL/:alias-drop NAME— local env aliases.:minimap on|off— corner mini-map of env health.:loglevel LEVEL— live-reload the tracing filter.
Per-env inspection
:why— Red-env diagnostic overlay (recent events / alarms / instance health / recent deploys; main + DLQ peek for Worker envs). Bound to!on the env table.:diff NAME— side-by-side env comparison.:resources/:res—DescribeEnvironmentResourcesdump.:alarms— CloudWatch alarms referencing the env.:versions— application versions (deployed marker, total count, deploy hint).:saved-configs/:configs— saved configuration templates (interactive:aapply,iinspect,xdelete,ccreate).:custom-platforms/:platforms— custom EB platforms.:plugins— list user plugin commands.:history— recent status / error log.:pending/:in-flight— overlay of dispatched actions + outcomes.:whatsnew— embedded changelog.:about/:credits— version, license, attributions.:update— show (and yank to clipboard) the upgrade command for whichever install channel (Homebrew / cargo-bin / tarball) ebman was installed from.:settings— interactive form to edit~/.config/ebman/config.toml; writes back on submit and live-applies theme / icons / refresh interval.
Write — env state
:rebuild/:restart/:terminate— action menu shortcuts (Terminate requires typed-name confirm).:deploy LABEL— ship an existing application version to the selected env.:deploy LABEL --preview— open a side-by-side overlay of the currently-deployed version vs the candidate (label, description, S3 source, timestamp + rollback / traffic warnings) without dispatching.:deploy --from PATH [--label L] [--describe D] [--no-deploy]— upload a local.zip(or--from s3://bucket/key), register a new version, optionally deploy.:upgrade [ARN]— list compatible platforms; with ARN, dispatch the migration.:clone NEWNAME— clone the selected env.:scale N/:stop/:start— set ASG min=max=N / 0 / 1.:capacity— modal form to edit Min / Max / Instance type / Cooldown in one shot (pre-filled fromDescribeConfigurationSettings).:swap TARGET— swap CNAMEs (Y/N confirm).:abort— abort an in-flight env update.
Write — env config
:env list | set KEY VAL | unset KEY— application env-var editor.:tag KEY VALUE/:untag KEY— env tag editor.:set-option NAMESPACE OPTION VALUE/:unset-option NAMESPACE OPTION— generic option-settings escape hatch.:instance-type TYPE— EC2 instance type (e.g.t3.medium).:keypair NAME/:service-role ARN/:instance-profile NAME— security tab.:public-ip on|off/:elb-scheme public|internal— network tab.:subnets/:elb-subnets/:security-groups— MultiSelect picker forms pre-filled with the env's current selection (lists available subnets / SGs from the VPC).:deployment-policy AllAtOnce|Rolling|RollingWithAdditionalBatch|Immutable|TrafficSplitting— deploy policy.:rolling-update on|off— ASG rolling-update policy.:health-check-url /path— HTTP health-check path.:logs-stream on|off [--retention DAYS]— toggle CW Logs streaming.:logs-tail [LOG_GROUP]— open a live streaming overlay for a CW Logs group (auto-picksweb.stdout.log).:notify EMAIL_OR_SNS_ARN | off— notification endpoint.:managed-window DAY HOUR | off— managed-platform-updates window.
Write — application versions / configs / alarms / platforms
:delete-version LABEL [--force]— delete an application version (with optional source-bundle removal).:config-save NAME/:config-apply NAME/:config-delete APP NAME/:config-inspect NAME— saved-configuration templates.:alarm-create NAME KIND THRESHOLD [OP]— CloudWatch alarm (KIND:health,4xx,5xx,latency).:alarm-delete NAME— remove a CW alarm.:custom-platform-delete ARN— delete a custom EB platform.:metric add LABEL NAMESPACE NAME [STAT] [DIM=VAL,...]/:metric remove LABEL/:metric list— custom Metrics-tab charts.
Multi-account / multi-region
:region all— fan acrossextra_regions+ current.:account NAME— switch to a configured AssumeRole account (see Configuration).:accounts— list AWS-Organizations child accounts; switch hints rendered for those with a configuredaccounts.NAME.:find-env SUBSTRING— scan every profile in~/.aws/{config,credentials}and every configured AssumeRole account in REGION.:org-health— aggregate env / red counts per profile + per configured AssumeRole account.
Multi-env
space— toggle multi-select.:batch-rebuild/:batch-restart— dispatch a non-destructive action across the selection.:batch-deploy LABEL— deploy the same version to every selected env in parallel.:batch-tag KEY VALUE/:batch-untag KEY— fan a tag write across the selection.:batch-set-option NAMESPACE OPTION VALUE— fan an option-settings write across the selection.:deselect/:select-clear— clear selection.
Output / yank
:export— yank filtered view as TSV.:json— yank filtered view as JSON array.:report/:markdown— yank filtered view as a Markdown table.
Read-only mode
:readonly on|off— toggle.--read-onlyon the CLI also locks every write surface.
Configuration
~/.config/ebman/config.toml:
# Refresh interval in seconds (default 15).
= 15
# Extra regions to expose in the region picker, comma-separated.
= ""
# Theme: "dark" (default), "light", or "high-contrast".
= "dark"
# Glyph set: "unicode" (default), "ascii" for low-feature terminals,
# "powerline" (alias "nerd") for Powerline-patched / Nerd Fonts, or
# "auto" to probe the terminal at startup and pick powerline if its
# support is detected (one-cell U+E0B0 advance), unicode otherwise.
= "unicode"
# Per-profile theme override — pin a theme per AWS profile so the screen
# itself says "you're in prod" without reading the breadcrumb. Format:
# "PROFILE:THEME,PROFILE:THEME". Theme names match the `theme = ...` key.
= "prod:high-contrast,staging:dark"
# Start with these toggles on (state.toml takes precedence after first run).
= false
= false
# Notification bell on increase in Red-env count.
= false
# Tag policy — flag envs missing any of these tags in the Config tab.
= "Owner,Project"
# Webhook URL to POST to when an env transitions to Red.
= ""
# AssumeRole targets reachable via `:account NAME`. One stanza per
# account. `source_profile` carries the base creds for the
# sts:AssumeRole call. `external_id` and `region` are optional.
# The temporary credentials build a fresh SdkConfig carrying only the
# assumed-role identity — source-profile creds never leak into request
# signing once the switch lands.
= "arn:aws:iam::111122223333:role/EbmanReadOnly"
= "default"
= "eu-west-2"
# accounts.prod.external_id = "..."
~/.config/ebman/keys.toml (optional) — custom keybindings:
# Aliases must be F1-F12 or an uppercase A-Z; map to a :command.
= "refresh"
= "region us-east-1"
= "history"
~/.config/ebman/commands.toml (optional) — user plugin commands. Each :NAME substitutes {name} / {cname} / {application} / {tier} / {region} / {profile} placeholders and yanks the rendered command to the clipboard.
[]
= "aws ssm start-session --target $(aws ec2 describe-instances --filters Name=tag:elasticbeanstalk:environment-name,Values={name} --query 'Reservations[].Instances[].InstanceId' --output text) --profile {profile}"
= "Yank a tunnel command into clipboard"
~/.config/ebman/state.toml is managed by the app — filter / sort / cursor position / named filters / saved views / pinned envs / custom metrics live there.
Headless interface (--control-socket)
Launch ebman with --control-socket PATH to expose a Unix-socket interface. A second binary, ebman ctl <op>, is the one-shot client (defaults to ~/.cache/ebman/control.sock).
Useful for integration tests, screenshot capture, scripted workflows.
What's stored locally
~/.config/ebman/config.toml— user configuration (see above).~/.config/ebman/keys.toml— optional custom keybindings.~/.config/ebman/commands.toml— optional plugin commands.~/.config/ebman/state.toml— persisted UI state: profile, region, filter, sort, grouping, redact, selected env, named filters, saved views, pinned envs, aliases, hidden columns, custom metrics. No credentials.~/.cache/ebman/ebman.log— application log; rotates as needed. SetRUST_LOG=debugfor verbose output.~/.cache/ebman/audit.log— every dispatched action and outcome (account, profile, region, action, target). Rotates at 1 MiB toaudit.log.1.~/.cache/ebman/crash-*.log— panic backtraces (10 most recent kept; 30-day TTL).- Clipboard —
y/Y/^Y/^Wwrite viaarboard.
Safety model
- Read-only mode (
--read-onlyor:readonly on) disables every write surface: action menu, DLQ resend / purge, all:-commands that mutate state. A greenREAD-ONLYpill in the header makes it visible. - Strict-typed confirm for irreversible actions: typing the env name is required to Terminate; typing the literal string to Purge.
- Pre-flight checks in the confirm modal:
DescribeInstancesHealthimpact count, last 3 events, traffic warnings for env-in-deploy / recently-changed / currently-Red. - Audit log records dispatch + outcome of every action.
Privacy / telemetry
Ebman does not phone home. There is no usage telemetry, no anonymous identifier, no crash auto-reporting, no third-party analytics endpoint. The only outbound HTTP from ebman itself is to AWS (the SDK calls you'd expect) and a single version-check ping to crates.io (update_check.rs), which crates.io logs as "client IP requested ebman version metadata" and nothing more.
Bug reports are operator-driven via :report-bug. Ebman builds a scrubbed payload locally — version / OS / icons / theme / refresh interval / last 30 log lines / last 10 on-screen messages / latest panic backtrace — and runs it through a redactor that strips account IDs (any 12-digit ASCII number), ARNs (arn:aws:*), every env name + application name + CNAME currently in the in-memory table, and the active profile name. The result lands in an overlay where you see the exact bytes before they leave the machine. Two delivery paths, both initiated by you:
ycopies the scrubbed payload to clipboard. Paste into a new GitHub issue.bopens https://github.com/tombaldwin/ebman/issues/new in your browser with the body pre-filled via URL params (truncated at ~7900 chars so the URL stays under GitHub's 8k limit).
Ebman never sends the payload itself. The redactor isn't bulletproof — a freeform error message could still embed a customer name in an unscrubbable shape — which is why you review the payload before pasting / opening the browser.
Crash logs are written locally to ~/.cache/ebman/crash-*.log by the panic hook (10 most recent kept, 30-day TTL). They're plain-text files; do whatever you want with them.
Distribution
- Cargo:
cargo install ebmanfrom crates.io;cargo install --path .from a checkout. - GitHub Releases: tagging
v<X.Y.Z>triggers.github/workflows/release.yml, which builds release binaries forx86_64-unknown-linux-gnu,aarch64-apple-darwin, andx86_64-apple-darwinand attaches tarballs + SHA-256 checksums to a draft release. - Homebrew: tap lives at
tombaldwin/homebrew-tap. Per-release: bump the version + 3 platform SHAs inFormula/ebman.rbin both this repo (forbrew install --formula PATH) and the tap.
Development
See BACKLOG.md for in-flight and planned work. See CLAUDE.md for the AI-assisted-contributor rules (the project has been developed heavily with Claude Code).
License
Dual-licensed under MIT or Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.