Skip to main content

Live, keyboard-driven terminal UI for Docker: Compose-aware, real-time stats, zero polling — manage containers, images, volumes, and networks without leaving the terminal

Project description

docksurf

A keyboard-driven terminal UI for visualising and managing Docker resources like containers, images, volumes, and networks. Compose-aware and live: it reacts to Docker events on its own and streams real time resource usage, so you're observing, not polling. No GUI, no browser tab.

docksurf's container tab

Docs: Quickstart · Full keybindings reference · Changelog

Highlights

  • Live by default — the tables auto-refresh on docker events (container start/stop/die, image pull/delete, …); r is a manual reload.
  • Local or remote — honours your active docker context on startup, so it manages the same daemon your CLI does: local, Docker Desktop, Colima, or a remote host over SSH/TCP.
  • Docker Compose aware — containers are grouped by project into a collapsible tree, with project-wide up / down / stop / start / restart and interleaved, colour-coded-per-service logs.
  • Live resource stats — CPU %, memory, network and block I/O streamed into the detail pane for the selected running container.
  • Full lifecycle control — pause/unpause and kill sit alongside stop/start/restart, so a stop that hangs on its 10s timeout never needs the CLI.
  • Multi-select + bulk actions — mark rows on any tab and stop/start/remove them as a batch; ideal for cleaning up after a test run.
  • Inspect & prune — the full raw docker inspect JSON for any resource in a searchable modal, plus a one-key menu to prune stopped containers, dangling images, unused volumes/networks, or everything at once.
  • Image / volume / network operations — pull images with live progress, view layer history, tag, and bulk-clean dangling images; create volumes and check per-volume disk size; create networks and connect/disconnect containers, with per-container IP/MAC in the detail pane.
  • Power-user exec & copy — a custom exec command with a chosen user, docker cp in/out of a container, and an on-demand docker top process snapshot, all via quick prompts.
  • Full control log viewer — live follow, in-log search with n/N match jumping, a timestamps toggle, configurable tail depth and --since window, line wrap for long JSON, jump-to-top/bottom, and one-key export of the buffer to a file.
  • Operational signals at a glance — colour-coded health column, uptime and restart count, plus recent health-check probe output in the detail pane.
  • Disk usage — a docker system df breakdown (per-type size + reclaimable) on demand.
  • In-app context switching — list and switch Docker contexts from inside the TUI (D) without ever running docker context use, so other terminals keep whatever context they already had. The choice is remembered across restarts.
  • Auto-reconnect — if the daemon goes down mid-session, the status bar flags it immediately and DockSurf reconnects on its own the moment it's back, with a full refresh — no restart required.

Requirements

  • Python 3.11+
  • A reachable Docker daemon (local, or a remote one via docker context)
  • uv (package manager)
  • The docker CLI on PATH — only needed for exec-shell (e/E), Compose project actions (u/k), and file copy (C); everything else uses the SDK. DockSurf degrades gracefully if it's absent.

Install

See QUICKSTART.md for install + first steps in under 2 minutes.

From PyPI:

pip install docksurf
docksurf

Or run it without installing, via uvx:

uvx docksurf

From source (for development, or to run an unreleased change):

git clone <repo>
cd docksurf

uv venv && source .venv/bin/activate
uv pip install -e .

docksurf

Or without installing:

uv run python -m docksurf_py.app

Releases

Published to PyPI via a tag-triggered GitHub Actions workflow (.github/workflows/publish.yml): pushing a vX.Y.Z tag builds the package and publishes it using PyPI Trusted Publishing (OIDC — no stored credentials), gated behind a manual approval step. See CHANGELOG.md for what's in each release.

Keybindings

The essentials — full reference (per-tab keys, log pane, Compose header behaviour) lives in KEYBINDINGS.md.

Key Action
? Help screen — every keybinding, in-app
r Refresh all Docker data
/ Search / filter the active tab
↑/↓, Tab Navigate rows / switch tabs
1-4 Jump directly to Containers / Images / Volumes / Networks
s/S/x Stop / start / restart (Containers tab)
e Exec shell into the focused container
l Toggle log viewer
space Mark for a bulk action
d Delete the selected — or marked — resource(s)
i Inspect (raw docker inspect JSON)
P Prune menu
D Switch Docker context
q Quit

Tabs

Every tab has a leading mark column (space to toggle) for multi-select + bulk actions — see Keybindings.

Containers — all containers (running and stopped), grouped by Compose project into a collapsible tree (project header → service rows), with standalone containers listed below. Columns: name, status, colour-coded health, and uptime. The detail pane shows image, ports, networks, project/service, uptime, restart count, a collapsible environment-variable section, a collapsible recent-health-probe log, a live CPU/mem/net/block-IO panel for a running container, and — on demand (t) — a docker top running-process snapshot.

Images — all images, tagged as In Use, Unused, or Dangling. Detail pane shows size, created date, architecture, and which containers reference the image. Pull new images with live progress (+), view per-layer history (h), retag (y), and one-key mark-all-dangling for bulk cleanup (a).

Volumes — all volumes, tagged as In Use or Orphaned. Detail pane shows mountpoint, driver, labels, and attached containers. Create volumes (+) and pull on-demand per-volume size on disk (b).

Networks — all networks with driver and scope. Detail pane shows driver, scope, subnet, gateway, and each attached container's IP/MAC within the network. Create networks (+) and connect/disconnect containers (v/m).

Architecture

Eleven top-level modules/packages with strict layering (models.py and constants.py are leaf nodes and nothing imports into them). Four of the eleven — docker, renderer, actions, widgets — are packages split into one file per class/mixin, each re-exporting exactly what the equivalent single file used to expose, so nothing outside the package needed to change:

  • constants.py — widget/tab/table IDs, the SafeMarkup render-boundary marker, and Rich markup helpers.
  • models.py — typed dataclasses for every resource (Container, Image, Volume, Network, ComposeProject, ContainerStats, ContainerTop, SystemDf, ContextInfo, …). No presentation logic.
  • connection.py — Docker connection detection/classification, context and host resolution.
  • docker/ — all Docker I/O via the Docker SDK for Python. DockerClient (client.py) owns the SDK connection (honouring docker context, with an in-app override for switch_context) and every management call — full container lifecycle (stop/start/restart/pause/kill/remove), prune, inspect, top, file copy, and context listing/switching; it also detects a dropped daemon (mark_disconnected) so reconnecting doesn't need a restart. DockerResourceFetcher (fetcher.py) does the parallel reads; LogStream/MergedLogStream/StatsStream/EventStream (streams.py) are the live iterators; context resolution (context.py) and display formatting (format.py) round out the package.
  • service.py — the DockerService protocol DockerClient implements (swappable in tests).
  • widgets/ — Textual UI components with no Docker knowledge, one file per widget/screen: ContainerTable, DetailPane, LogPane, SearchBar, ConfirmDialog, HelpScreen, SystemDfScreen, InspectScreen, PruneScreen, PromptScreen, StatusBar (renders a connection-lost indicator).
  • renderer/, actions/, search.py, observability.py — focused mixin classes composed into DockSurfApp in app.py, driven by a single per-tab ResourceEntry registry: table rendering, snapshot/event lifecycle, connection-state tracking, and multi-select marking (renderer/); container lifecycle, Compose, delete, bulk actions, inspect, prune, exec, file-copy, and context-switching actions (actions/); search (search.py); live stats, disk usage, and docker top (observability.py).

How data is fetched

DockSurf talks to Docker through the SDK (docker-py), not the CLI but with three sanctioned exceptions, all guarded on the docker CLI being present: the interactive exec-shell (needs a real TTY the SDK can't hand back), Compose project actions (docker-py has no Compose support, and up must recreate containers from the compose file), and file copy (docker cp) — the SDK only exposes raw tar archives, and safely reproducing docker cp's copy semantics isn't worth it for a convenience feature.

  • Snapshots — all four resource types are fetched in parallel via ThreadPoolExecutor on each refresh, so the UI never blocks.
  • Event-driven refresh — a background worker subscribes to docker events and debounces bursts into a refresh, ignoring high-frequency noise (exec probes, per-interval health-status pings). A refresh preserves your current selection rather than resetting the cursor.
  • Live statscontainer.stats(stream=True) is streamed for the selected running container on a background thread and rendered into the detail pane (one stream at a time, to stay cheap).
  • Logs — streamed live from the SDK's generator; timestamps are always fetched (shown on demand), and tail depth / --since window are configurable per view. A Compose project's logs are merged across its containers with a colour-coded per-service prefix.

Docker contexts — local and remote

DockSurf connects to whatever daemon your active Docker context points at (matching the docker CLI's precedence: DOCKER_HOST → active context → default socket). That doesn't have to be your local machine, and — since context switching now happens in-app — it doesn't require restarting DockSurf either.

Creating a context is still a docker CLI step (DockSurf lists and switches contexts, it doesn't create them):

# A context pointing at a remote Linux host over SSH — any host with a
# reachable Docker daemon and SSH access works: a cloud VM, a bare-metal
# box, a Raspberry Pi, a home server.
docker context create prod --docker "host=ssh://user@prod.example.com"

# Or over plain TCP, if the daemon's API is exposed that way
docker context create staging --docker "host=tcp://staging.example.com:2375"

Switching contexts from inside DockSurf — press D to list every context docker context ls knows about and pick one. This is in-app only: it never runs docker context use, so it doesn't touch ~/.docker/config.json or repoint any other terminal's docker/docker compose — DockSurf just opens its own connection to the chosen context's daemon. The choice is remembered across restarts (~/.local/share/docksurf/state.json).

Auto-reconnect — if the daemon your active context points at goes down mid-session (VM reboot, daemon restart, network blip), the status bar flags it immediately (● <reason>) and DockSurf retries on its own every couple of seconds, reconnecting and refreshing the moment it's back — no restart, no manual r.

Common use cases:

  • Managing a Linux VM (cloud instance, on-prem box, home server) over SSH without keeping a shell session open
  • Switching between staging and production daemons from one tool, mid-session, with D
  • Inspecting a production server's containers without full shell access
  • Managing a Raspberry Pi or other edge device from your laptop

Any endpoint that speaks the Docker Engine API works this way — a plain Linux daemon, Docker Desktop, Colima, Rancher Desktop, or a remote host over SSH/TCP. Managed platforms that don't expose that API can't be reached this way, even with a custom context — Azure Container Apps is the common trap: it's Kubernetes-based under the hood with no Docker Engine endpoint at all, so docker context/DockSurf/docker ps have nothing to talk to. Azure Container Instances had a (now-deprecated) docker context create aci integration; Container Apps never did — manage those via az containerapp, the Azure Portal, or the Azure SDK instead.

Logging

App logs are written to ~/.local/share/docksurf/docksurf.log — never to stdout (which belongs to the TUI). Useful for debugging refresh errors, failed Docker API calls, container/Compose action results, and stream lifecycle events.

Changelog

See CHANGELOG.md for release history.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

docksurf-0.2.0.tar.gz (253.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

docksurf-0.2.0-py3-none-any.whl (126.8 kB view details)

Uploaded Python 3

File details

Details for the file docksurf-0.2.0.tar.gz.

File metadata

  • Download URL: docksurf-0.2.0.tar.gz
  • Upload date:
  • Size: 253.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for docksurf-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0fc6f1b83551bb0ce12ebfb28c68795de3575f6a79ba6914cf4f093d04b95300
MD5 fd2f8cb9ce29ce2d8ca436cd53aecf24
BLAKE2b-256 ba6de2e7ab2469df710f5ade03e04d78c92270260fd8b0c76f4170ac0a99d886

See more details on using hashes here.

Provenance

The following attestation bundles were made for docksurf-0.2.0.tar.gz:

Publisher: publish.yml on praneeth-etta/docksurf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docksurf-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: docksurf-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 126.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for docksurf-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c5ccb289242cf8924c9938a3be5124c24f8d21769ead2eff8b0dd5d698fdbbf
MD5 938c9713d7a3853d5d41341adb9691ab
BLAKE2b-256 b058dd0e28ad1cb5d91f7040008268309557cd385f9ca108657e52e5248bfa65

See more details on using hashes here.

Provenance

The following attestation bundles were made for docksurf-0.2.0-py3-none-any.whl:

Publisher: publish.yml on praneeth-etta/docksurf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page