- Go 99.8%
- Makefile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| .opencode | ||
| cmd | ||
| docs | ||
| internal | ||
| .gitignore | ||
| .tagme-authors.json | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| cliff.toml | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| Makefile | ||
| README.md | ||
| SECURITY-REVIEW.md | ||
| SECURITY.md | ||
pods-ctl
A Go CLI and TUI for managing Podman pods as systemd services, supporting both rootless (user session) and rootful (system-level) Podman.
Description
pods-ctl talks to systemd over D-Bus and to Podman over its REST API, so you can list, start, stop, restart, inspect, pull, upgrade, prune, and browse pods without needing root privileges. It keeps a local SQLite registry of your quadlet .pod/.container definitions, resolves pod-service-container mappings, detects image and healthcheck drift, and exposes everything through both a traditional command set and an interactive terminal UI. Image drift detection and image pulling work even when pods are stopped, reading declared images directly from quadlet .container files.
Core value proposition: reliably manage Podman pods as systemd services with clear status, health checks, declared-vs-running configuration drift detection, and lifecycle control from a single self-contained binary. Both rootless (user session) and rootful (system-level) Podman are supported.
Features
CLI commands
browse— launch the interactive TUIstart [target...],stop [target...],restart [target...]— lifecycle controlstatus [target...]— show pod status and per-container healthcheck configuration, exits non-zero on failureslist— registry view with live container count and statusdiscover— scan the configured base directory and populate the registrycheck— cross-check registry against on-disk.podfiles under the configured base directorycleanup— remove stale registry entriesvalidate [target...]— validate registry entriesvalidate-config— validate configuration, registry, and runtime connectivityhealth [target...]— deep container health check with declared vs running healthcheck config and drift detection; reports unhealthy when expected containers are missing from Podmanreload [target...]— runsystemctl --user daemon-reloadrecover [target...]— start stopped/exited/unhealthy containers and start missing containers not found in Podman but declared in the registry cacheprune [target...]—podman system pruneper pod userupdate [target...]—podman auto-updateper pod userpull [target...]— pull all declared container images for pod user(s); works with stopped pods by readingImage=from quadlet.containerfiles via systemdupgrade [target...]— restart pods where the running image or healthcheck config differs from the quadletrefresh-deps [target...]— cache systemd dependency graph and image drift in DB; works with stopped podsremove [target...]— remove pods from the registry and clean up dependenciesimages [target...]— list collected container images and versionsstats [target...]— live CPU/memory/PID/network usage per containerevents— stream Podman lifecycle eventslogs <target>— stream container/pod logs (tail, follow, timestamps)export --output <path>— export registry data to JSON (-o -for stdout)import --input <path>— import registry data from JSON (-i -for stdin; merge or replace)completion [bash|zsh|fish|powershell]— generate shell completion scriptsman <dir>— generate Unix man pages
Global flags: --no-color, --json, --dry-run, --quiet/-q, --plain, --no-input, --verbose/-v, --log-format, --timeout, --user, --compact, --watch, --base-dir, -f/--file, --config.
Destructive commands (stop, restart, prune, upgrade, remove) accept --force to skip confirmation prompts.
TUI features (pods-ctl browse)
- Tree view: users → pods → containers with live status, health, CPU%, memory, restart counts, and sparkline CPU history
- Tabbed right-hand stack: Pod summary, Container summary, Details/Inspect, and Diff panes
- Details pane with an interactive, collapsible JSON tree of the full
podman inspectoutput for the selected pod or container - Diff pane: compare running vs quadlet-declared images for a pod or container
- Logs pane: live container/pod log stream with bounded scrollback, follow/pause toggle, and
/search filtering - Events pane: live Podman lifecycle event stream that resumes from the last seen time
- Health overlay (
h): per-container status/health/restarts/exit-code table with recent failure messages, healthcheck configuration indicators, and drift markers when declared and running configs differ - Container details pane: dedicated Healthcheck section showing the declared quadlet config, the running
podman inspectconfig, and any drift fields - Bulk action palette (
Ctrl+a/.) with grouped actions, select-all/invert, and clear selection - Filtering, sort toggles, pinned/favorite pods, undo stack for container actions, and toast notifications
- Full mouse support: click to focus panes, scroll wheel, double-click to open logs, right-click context menu, and clickable overlays
- Keyboard-driven interface with context-aware help overlay
Installation
Build the self-contained binary from source (requires Go 1.26+):
make build
The Makefile stamps the version, commit, and build date from git using -trimpath and -ldflags "-s -w -X ...cli.Version=... -X ...cli.Commit=... -X ...cli.BuildDate=...". To build manually:
go build -trimpath -ldflags "-s -w \
-X git.desord.re/desordre/pods-ctl/internal/cli.Version=$(git describe --tags --always) \
-X git.desord.re/desordre/pods-ctl/internal/cli.Commit=$(git rev-parse --short HEAD) \
-X git.desord.re/desordre/pods-ctl/internal/cli.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o pods-ctl .
Install to /usr/local/bin (default prefix):
sudo make install PREFIX=/usr/local
The resulting binary is pods-ctl.
Quick start
# Scan quadlet definitions under the configured base directory and populate the registry
pods-ctl discover
# Show all registered pods with live status
pods-ctl list
# Check overall pod health and healthcheck config drift
pods-ctl health
# Start, stop, or restart pods (target is quadlet pod name, optionally with :container)
pods-ctl start myapp
pods-ctl stop myapp:web
pods-ctl restart myapp
# Pull declared images (works even if the pod is not started)
pods-ctl pull myapp
pods-ctl pull # all pods
# Upgrade pods where the running image or healthcheck differs from the quadlet
pods-ctl upgrade
# Watch live status
pods-ctl status --watch
# Launch the interactive TUI
pods-ctl browse
# Stream Podman events
pods-ctl events
# Scripting: plain text output (tab-separated, one record per line)
pods-ctl list --plain
# Scripting: suppress non-essential output
pods-ctl start myapp --quiet
# Scripting: JSON output piped to jq
pods-ctl status --json | jq '.pods[] | select(.status=="failed")'
# Force destructive action without confirmation (for scripts/CI)
pods-ctl stop --force
pods-ctl prune --force
System requirements
- Linux with systemd and D-Bus
- Rootless Podman with the user socket available at
/run/user/{uid}/podman/podman.sock, or rootful Podman with the system socket at/run/podman/podman.sock - Go 1.26+ for building from source
- A terminal with TrueColor support recommended for the TUI
Configuration
pods-ctl loads configuration from:
- A config file passed with
--config /etc/pods-ctl/config.*$HOME/.config/pods-ctl/config.*- Environment variables prefixed with
PODS_CTL_(for example,PODS_CTL_REGISTRY)
Supported configuration keys: registry, base_dir, no_color, json, dry_run, quiet, plain, no_input, verbose, log_format, timeout, user, all, compact, watch. The default registry database is /etc/pods/registry.db and the default base directory for quadlet definitions is /srv/storage.
Environment variables: PODS_CTL_* prefix for all config keys (e.g. PODS_CTL_REGISTRY), plus NO_COLOR and DEBUG for color and verbosity control, and standard proxy vars (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY).
See docs/configuration/ for detailed configuration guidance.
Architecture overview
main.go
│
▼
cmd/ ← Cobra command tree and shared helpers
┌─────────┐
│ browse │ ────────────┐
│ start │ │
│ stop │ ▼
│ ... │ internal/tui ← Bubble Tea model, panes, keybindings
└─────────┘ │
│ │
▼ ▼
internal/config internal/podman ← HTTP-over-Unix-socket Podman client
internal/cli │
(version, commit, │
build date) ▼
│ internal/systemd ← D-Bus / systemctl --user client
│ │
▼ ▼
internal/registry ←─── SQLite database via internal/db
│
▼
internal/healthcheck ← shared declared-vs-running healthcheck drift comparison
cmd/— CLI surface. Each subcommand is a separate file; shared logic lives incmd/common.go,cmd/presenter.go,cmd/upgrade_helper.go, and related helpers.internal/cli/— Build-time version, commit, and build-date strings injected via-ldflagsintocli.Version,cli.Commit, andcli.BuildDate; referenced bycmd.Versionfor--versionand displayed by the TUI loading spinner.internal/tui/— Bubble Tea-based terminal UI (browse.go, panes, keybindings, mouse dispatch, streaming state, and tests). The loading spinner animates at 100ms with a K-2000 (KITT) red half-height bar scanner sweep and shows version/commit/build-date metadata.internal/podman/— thin Podman REST API client with aPodmanClientinterface for testability.internal/systemd/— systemd user-session client over D-Bus with subprocess fallback tosystemctl --user.internal/registry/— filesystem discovery and registry CRUD againstinternal/db.internal/db/— SQLite schema forpods,images,pod_services,pod_deps, andcontainer_healthchecks.internal/healthcheck/— shared normalization and drift comparison between quadlet-declared and running Podman healthcheck configurations.internal/targets/— target parsing (podorpod:container) and filtering.internal/output/— colored terminal output, tables, JSON rendering, hints, quiet/plain modes, per-stream TTY color detection (stdout vs stderr), and ANSI sanitization of untrusted content.internal/config/— Viper-driven configuration.
TUI guide
Launch the browser with:
pods-ctl browse
Layout
┌────────────────────┬──────────────────────────────────────────┐
│ │ Tab 1: Pod summary │
│ Tree pane │ Tab 2: Container summary │
│ users → pods │ Tab 3: Details / inspect │
│ → containers │
│ ├──────────────────────────────────────────┤
│ │ Logs pane (toggle with l) │
│ │ Events pane (toggle with E) │
└────────────────────┴──────────────────────────────────────────┘
- Tree pane (left): navigate users, pods, and containers; inspect live status at a glance.
- Pod/Container/Details panes (right stack): tabbed information.
- Logs/Events panes (bottom tray): streaming views toggled on demand.
Keybindings
| Key | Action |
|---|---|
↑ / ↓ |
Move cursor in the pod tree or Details entries |
→ |
Expand the focused tree node or Details section |
← |
Collapse the focused node, or move to its parent if already collapsed or a leaf |
j / k |
Scroll/move cursor in the focused pane |
Enter / Space |
Expand the focused tree leaf or toggle the focused details section |
a |
Expand all sections (Details) or all tree nodes (Tree) |
x |
Collapse all sections (Details) or all tree nodes (Tree) |
PgUp / PgDn |
Page scroll in Logs/Events and other scrollable panes |
Tab / Shift+Tab |
Cycle pane focus forward/backward |
0–6 |
Focus pane directly: 0=Tree, 1=Pod, 2=Container, 3=Details, 4=Diff, 5=Logs, 6=Events |
l |
Toggle logs pane |
E |
Toggle events pane |
f |
Toggle follow mode on logs/events |
/ |
Search inside the Logs pane (or filter the tree when Logs is not focused) |
n / N |
Next/previous match in the Logs pane search |
s |
Cycle sort mode (name, status, health, CPU, memory) |
r |
Reload registry |
R |
Restart selected container |
S |
Stop selected container |
A |
Start selected container |
u |
Update selected container |
g |
Upgrade selected container if image mismatch (CLI upgrade also checks healthcheck drift; TUI action checks image drift only) |
d |
Open Diff tab for the selected pod or container |
U |
Undo last container action |
e |
Exec into selected container |
p |
Pin/unpin selected pod |
h |
Open health overlay for the selected pod or container |
space / a / i / x |
Toggle / select all visible / invert / clear selection |
. / Ctrl+a |
Open the bulk action palette |
c |
Toggle color |
Esc / q |
Cancel / close pane / overlay / quit |
? |
Show/hide context-aware help overlay |
Ctrl+C |
Quit (closes streaming panes first) |
Details-pane navigation: when the Details pane is focused,
↑/↓/j/kmove through JSON tree entries,→expands the current object or array, and←collapses it — or moves to the parent entry if already collapsed or a leaf.Enter/Spacetoggle expansion.a/xexpand/collapse every JSON node.
Footer hints: the bottom bar shows context-aware shortcuts that change based on the currently focused pane (tree, details, logs, events, etc.).
Filtering:
/opens a footer filter bar supportingname:,label:,group:, and plain terms that match name, label, status, and health. Seedocs/guides/browse.mdfor the full query syntax.
Destructive actions show a confirmation dialog; confirm with y/Enter and cancel with n/Esc/q.
Mouse interactions
The TUI supports mouse interactions across all panes and overlays:
| Action | Effect |
|---|---|
| Left-click tree row | Focus tree + move cursor to node |
| Left-click focused tree row | Toggle expand/collapse |
| Double-click pod/container | Open logs pane |
| Right-click tree node | Open action palette for that node |
| Left-click right-stack pane | Focus that pane |
| Left-click Details JSON row | Move JSON cursor / toggle node |
| Left-click logs/events pane | Focus the streaming pane |
| Left-click tab | Switch to that pane |
| Left-click overlay | Dismiss overlay |
| Left-click confirm Yes/No | Select / confirm / cancel |
| Left-click help entry | Select / preview key |
| Left-click palette action | Select / run action |
| Scroll wheel in tree | Move cursor up/down |
| Scroll wheel in panes | Scroll content |
| Scroll wheel on tab bar | Cycle focus |
| Scroll wheel in overlay | Scroll overlay content |
See docs/guides/browse.md for the full mouse reference.
Development
make all # vet, build, and test
make build # compile pods-ctl with version, commit, and date stamping
make test # run tests with -race
make vet # go vet ./...
make fmt # go fmt ./...
make lint # vet + optional golint/staticcheck
make security # govulncheck — scan dependencies for vulnerabilities
make man # generate man pages to /tmp/pods-ctl-man/
make tidy # go mod tidy && go mod verify
make coverage # generate and open coverage report
make clean # remove compiled binary
See docs/guides/development.md and docs/testing/testing.md for detailed development and testing guidance.
Roadmap
Current milestone: v1.2 Codebase Hardening & Quality
- P0 (complete): Bug fixes — showProperties arg ordering, loadErr not cleared, dryRun bypass in TUI, missing returns after osExit, auditLog memory leak, BaseDir config, withWatch panic safety, lifecycle JSON contract, events.go injectable user, dead code cleanup.
- P1 (complete): Dead code removal and deduplication — removed ~1,100 lines of dead/duplicated code. Consolidated shared utilities into
targetspackage. AddedClose()to podman client. - P2 (pending): Error handling and output — typed error propagation, io.Writer abstraction, context in DB layer, transactions.
- P3 (pending): Test infrastructure — shared fakes, batch-command helper, table-driven tests.
- P4 (pending): Architecture — decompose TUI God Object, wire Keymap, fix race conditions.
- P5 (pending): Refactoring — overlay boilerplate, status/list merge, orphaned entries cleanup.
- P6 (pending): New features —
diff,exec,doctor,history,create, structured logging, metrics export.
Previous milestone: v1.1 TUI Power User Features (complete)
- Phase 1 (complete): Live logs and Podman events streaming panes with bounded scrollback and clean stream lifecycle.
- Phase 2 (complete): Bulk action palette improvements, log search/filter, image drift Diff pane, health overlay, and CLI
logs/export/importcommands. - Phase 3 (complete): Interactive filter/search panel for the pod tree with footer input, live updates, and prefix-aware queries (
name:,label:,group:). - Phase 4 (complete): Start/stop/restart/refresh actions with confirmation, toast notifications, delayed post-action refresh (3s), and automatic container-reload retry (up to 3 attempts) to handle Podman container recreation latency.
- Phase 5 (complete): Context-aware help overlay and dynamic footer hints.
Deferred to v2.0/polish: live per-container CPU/memory stats pane with sparkline/bar history, structured error wrapping, full dependency-injection migration for SystemdClient/PodmanClient, command-level integration tests, and non-TUI stats/events refinement.
Completed in CLI polish pass: man page generation (pods-ctl man), --quiet/-q, --plain, --no-input, --force on destructive commands, per-command Example: help text, DEBUG/PODS_CTL_NO_COLOR env vars, per-stream TTY color detection, signal handling for events/watch mode, - stdin/stdout convention for export/import, and clig.dev compliance audit.
Out of scope: Kubernetes/OpenShift integration, a web UI or daemon mode, and non-Podman runtimes such as Docker or containerd.
License
License to be determined. See the repository for a LICENSE file once one is added.