No description
  • Go 99.8%
  • Makefile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ymage 176ece25d3
All checks were successful
/ build-test (push) Successful in 7m44s
/ release (push) Successful in 12s
chore(version): Release v0.46.9
2026-08-10 04:15:16 +02:00
.forgejo/workflows refactor(db): migrate from mattn/go-sqlite3 to modernc.org/sqlite 2026-07-19 16:51:55 +02:00
.opencode refactor(tui): split panes.go (1218 lines) into per-pane files 2026-07-07 00:03:45 +02:00
cmd fix(upgrade): restart drifted containers atomically to avoid pod cancellation 2026-08-08 12:38:15 +02:00
docs docs: add AGENTS.md and update documentation for P0/P1 codebase hardening 2026-07-31 23:26:16 +02:00
internal fix(recover): invalidate podman name cache after pod lifecycle actions 2026-08-08 13:06:19 +02:00
.gitignore chore: add .superpowers/ to .gitignore 2026-07-19 22:12:43 +02:00
.tagme-authors.json chore(version): Release v0.46.9 2026-08-10 04:15:16 +02:00
AGENTS.md docs: add AGENTS.md and update documentation for P0/P1 codebase hardening 2026-07-31 23:26:16 +02:00
CHANGELOG.md chore(version): Release v0.46.9 2026-08-10 04:15:16 +02:00
CLAUDE.md chore(version): Release v0.46.7 2026-08-08 12:38:59 +02:00
cliff.toml chore: add tagme author email mapping 2026-08-10 04:15:01 +02:00
go.mod feat(cli): comprehensive clig.dev compliance pass 2026-07-21 05:43:30 +02:00
go.sum feat(cli): comprehensive clig.dev compliance pass 2026-07-21 05:43:30 +02:00
main.go fix(security): comprehensive security hardening (6 MEDIUM, 7 LOW, 6 INFO) 2026-07-19 05:46:53 +02:00
Makefile feat(cli): comprehensive clig.dev compliance pass 2026-07-21 05:43:30 +02:00
README.md docs: add AGENTS.md and update documentation for P0/P1 codebase hardening 2026-07-31 23:26:16 +02:00
SECURITY-REVIEW.md docs: record P2 hardening fixes in SECURITY-REVIEW.md and TODO.md 2026-08-02 22:09:30 +02:00
SECURITY.md docs: update documentation for pure-Go SQLite driver migration 2026-07-19 16:52:04 +02:00

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 TUI
  • start [target...], stop [target...], restart [target...] — lifecycle control
  • status [target...] — show pod status and per-container healthcheck configuration, exits non-zero on failures
  • list — registry view with live container count and status
  • discover — scan the configured base directory and populate the registry
  • check — cross-check registry against on-disk .pod files under the configured base directory
  • cleanup — remove stale registry entries
  • validate [target...] — validate registry entries
  • validate-config — validate configuration, registry, and runtime connectivity
  • health [target...] — deep container health check with declared vs running healthcheck config and drift detection; reports unhealthy when expected containers are missing from Podman
  • reload [target...] — run systemctl --user daemon-reload
  • recover [target...] — start stopped/exited/unhealthy containers and start missing containers not found in Podman but declared in the registry cache
  • prune [target...]podman system prune per pod user
  • update [target...]podman auto-update per pod user
  • pull [target...] — pull all declared container images for pod user(s); works with stopped pods by reading Image= from quadlet .container files via systemd
  • upgrade [target...] — restart pods where the running image or healthcheck config differs from the quadlet
  • refresh-deps [target...] — cache systemd dependency graph and image drift in DB; works with stopped pods
  • remove [target...] — remove pods from the registry and clean up dependencies
  • images [target...] — list collected container images and versions
  • stats [target...] — live CPU/memory/PID/network usage per container
  • events — stream Podman lifecycle events
  • logs <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 scripts
  • man <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 inspect output 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 inspect config, 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:

  1. A config file passed with --config
  2. /etc/pods-ctl/config.*
  3. $HOME/.config/pods-ctl/config.*
  4. 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 in cmd/common.go, cmd/presenter.go, cmd/upgrade_helper.go, and related helpers.
  • internal/cli/ — Build-time version, commit, and build-date strings injected via -ldflags into cli.Version, cli.Commit, and cli.BuildDate; referenced by cmd.Version for --version and 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 a PodmanClient interface for testability.
  • internal/systemd/ — systemd user-session client over D-Bus with subprocess fallback to systemctl --user.
  • internal/registry/ — filesystem discovery and registry CRUD against internal/db.
  • internal/db/ — SQLite schema for pods, images, pod_services, pod_deps, and container_healthchecks.
  • internal/healthcheck/ — shared normalization and drift comparison between quadlet-declared and running Podman healthcheck configurations.
  • internal/targets/ — target parsing (pod or pod: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
06 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/k move 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/Space toggle expansion. a/x expand/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 supporting name:, label:, group:, and plain terms that match name, label, status, and health. See docs/guides/browse.md for 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 targets package. Added Close() 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/import commands.
  • 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.