dtop
CLAUDE.md1.5k starsChanged 2 days agoMITView on GitHub
dtop is a terminal-based Docker container monitoring tool built with Rust. It provides real-time CPU, memory, network, and disk I/O metrics for Docker containers through a TUI interface, with support for both local and remote (SSH/TCP) Docker daemons. The tool supports monitoring multiple Docker hosts simultaneously and includes a built-in log viewer for streaming container logs. The docs site is a SvelteKit static site in the docs/ directory. Use bun (not npm) for all docs commands. The animated TUI on…
We split this CLAUDE.md into the rules it gives an agent
33 rules. About 0 are common, 18 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈18
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 87
When releasing a new version, update the flake hashes:
- Very specific: line 95
Update the version in flake.nix
- Distinctive: line 97
Update all platform hashes automatically
- Distinctive: line 99
Test the updated flake with:
- Very specific: line 201
If config has all: true, the CLI flag cannot override it back to false
- Very specific: line 351
All events include the host_id to identify their source
- Very specific: line 352
Reconnects automatically: when the daemon goes away (e.g.
- Distinctive: line 392
Stop/Restart use 10-second timeout before force kill
- Distinctive: line 393
Remove uses force option to remove even if running
- Very specific: line 407
All container managers share the same event channel (mpsc::Sender<AppEvent>)
- Very specific: line 408
Every event includes a host_id to identify which host it came from
- Very specific: line 505
Only works when not in an SSH session (detected via SSH_CLIENT/SSH_TTY/SSH_CONNECTION env vars)
- Very specific: line 582
All fields except host are optional and use #[serde(skip_serializing_if = "Option::is_none")].
- Distinctive: line 585
If CLI hosts are explicitly provided (not default), they override config file
- Very specific: line 586
If CLI uses default (--host local) and config file has hosts, config file is used
- Very specific: line 587
If both are empty/default, defaults to local
- Very specific: line 624
Remove needs a second Enter (armed_bulk_action), and only runs if it still hits the same containers (armed_bulk_targets, compared as a set …
- Very specific: line 668
per connection: If a remote lowers MaxSessions below 8, channel opens there will fail and the master gets evicted.
- Very specific: line 676
If opening a channel fails (dead master after a network drop or daemon restart), that master is evicted and the attempt retries once on a …
- Distinctive: line 756
Search bar at bottom when in SearchMode (filters containers as you type)
- Distinctive: line 779
Default sort: Uptime (newest first, descending)
- Distinctive: line 792
Search filtering is case-insensitive and filters by both name and ID
- Very specific: line 920
Run cargo release <version> to create a new release
- Very specific: line 1021
Run tests with cargo test or cargo insta test for snapshot tests.
- Distinctive: line 1034
Prefer extending an existing view (e.g.
- Distinctive: line 1035
Stop at ~1,000 lines: If a PR's non-test, non-snapshot diff passes ~1,000 lines, stop and check scope with the user before continuing.
- Distinctive: line 1040
Subagents and workflows get the out-of-scope list: Every implementation prompt includes "not in scope" and the rule: a deviation that adds …
- Distinctive: line 1050
Audit size before review, not after: When a body of work is done, report its size per feature (code, tests, snapshots) next to its user …
- Distinctive: line 1058
Before committing or submitting a pull request, always run:
- Distinctive: line 1066
When reviewing pull requests for this repository, follow these guidelines:
- Distinctive: line 1074
Skip minor nitpicks: - Don't comment on trivial style issues unless they significantly affect readability
- Distinctive: line 1075
Be direct: - Use short, clear sentences.
- Distinctive: line 1076
Group related feedback: - Don't create separate comments for each small issue
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
dtop is a terminal-based Docker container monitoring tool built with Rust. It provides real-time CPU, memory, network, and disk I/O metrics for Docker containers through a TUI interface, with support for both local and remote (SSH/TCP) Docker daemons. The tool supports monitoring multiple Docker hosts simultaneously and includes a built-in log viewer for streaming container logs.
Build & Run Commands
# Development
cargo run # Run with local Docker daemon (or config file)
cargo run -- --host ssh://user@host # Run with remote Docker host via SSH
cargo run -- --host tcp://host:2375 # Run with remote Docker host via TCP
cargo run -- --host tls://host:2376 # Run with remote Docker host via TLS
cargo run -- --host local --host ssh://user@host1 --host tcp://host2:2375 # Multiple hosts
cargo run -- --filter status=running # Filter to show only running containers
cargo run -- --filter name=nginx --filter label=env=prod # Multiple filters
cargo run -- --all # Show all containers (including stopped/exited)
cargo run -- -a # Short version of --all
cargo run -- --sort name # Sort containers by name
cargo run -- -s cpu # Sort containers by CPU usage
# Self-update
cargo run -- update # Update dtop to the latest version
dtop update # (or use the installed binary)
# Testing & Quality Checks
cargo fmt --check # Check formatting (must pass before committing)
cargo clippy # Run linter (must pass before committing)
cargo test # Run all tests
cargo test -- --nocapture # Run tests with output
cargo insta test # Run tests with snapshot review
cargo insta accept # Accept all pending snapshots
cargo insta reject # Reject all pending snapshots
# Production build
cargo build --release # The binary will be at target/release/dtop (includes self-update)
cargo build --release --no-default-features # Build without self-update feature (smaller binary)
# Changelog generation
git-cliff --latest # Generate changelog for the latest tag
git-cliff --unreleased # Generate changelog for unreleased changes
git-cliff --tag v0.3.0..v0.3.6 # Generate changelog for a version range
git-cliff -o CHANGELOG.md # Write changelog to file
# Docker build
docker build -t dtop .
docker run -v /var/run/docker.sock:/var/run/docker.sock -it dtop
# Nix
nix run . # Run using pre-built binary (fast)
nix run .#source # Run building from source
nix build # Build the package
nix flake check # Verify flake is valid
nix develop # Enter dev shell with Rust toolingDocumentation Site
The docs site is a SvelteKit static site in the docs/ directory. Use bun (not npm) for all docs commands.
cd docs
bun install # Install dependencies
bun run dev # Start dev server
bun run build # Production build
bun run preview # Preview production buildThe animated TUI on the home page (docs/src/lib/components/TuiDemo.svelte) hand-ports dtop's
rendering (column widths, header bar fitting, formatters, icons). Update it when changing
src/ui/container_list.rs, src/ui/system_header.rs, src/ui/formatters.rs or the default columns.
Nix Flake
The project includes a Nix flake (flake.nix) for reproducible builds and easy installation.
Packages
packages.default- Pre-built binary from GitHub releases (instant install)packages.source- Build from source usingbuildRustPackage
Updating Nix Hashes (for new releases)
When releasing a new version, update the flake hashes:
./scripts/update-nix-hashes.sh <VERSION>
# Example: ./scripts/update-nix-hashes.sh 0.6.8This script requires Nix to be installed. It will:
- Update the version in
flake.nix - Fetch new release artifacts and compute their hashes
- Update all platform hashes automatically
Test the updated flake with:
nix build && ./result/bin/dtop --versionConfiguration
The application supports configuration via YAML files. Config files are searched in the following order (first found wins):
./config.yamlor./config.yml./.dtop.yamlor./.dtop.yml~/.config/dtop/config.yamlor~/.config/dtop/config.yml~/.dtop.yamlor~/.dtop.yml
--config <PATH> / -c <PATH> loads that file instead and skips the search. It must exist
(a missing or invalid file is an error), it is loaded even when --host is given (--host
still replaces its hosts), and Ctrl-S saves back to it.
Command line arguments take precedence over config file values.
Example config file (config.yaml):
hosts:
- host: local
- host: ssh://user@server1
filter:
- status=running
- label=environment=production
- host: tcp://192.168.1.100:2375
- host: ssh://root@146.190.3.114
dozzle: https://l.dozzle.dev/
filter:
- name=nginx
- ancestor=ubuntu:24.04
# Icon style: "auto" (default), "unicode" or "nerd" (requires Nerd Font)
icons: auto
# OSC 8 hyperlinks on container names: "auto" (default), "always", or "never"
hyperlinks: auto
# System stats header above the container list (default: true; 't' toggles)
stats_header: true
# Show all containers (default: false, shows only running containers)
# Set to true to show all containers including stopped, exited, and paused containers
all: false
# Default sort field: "uptime" (default), "name", "cpu", or "memory"
sort: uptimeEach host entry is a struct with:
host: Docker connection string (required)name: Optional display name shown in the header and Host column (defaults to a name derived fromhost, e.g.user@server1)dozzle: Optional URL to Dozzle instancefilter: Optional list of Docker filters (e.g., ["status=running", "name=nginx"])- Future optional fields can be added as needed
Global config options:
icons: Icon style to use ("auto", "unicode" or "nerd").auto(ui/icons.rs::IconMode::resolve) sends XTVERSION followed by DA1 right after raw mode is enabled and before the keyboard worker starts (which would otherwise eat the reply). DA1 is the sentinel: every terminal answers it, so a terminal that ignores XTVERSION costs one round trip, not the 1s timeout. Nerd icons on Ghostty, WezTerm and kitty >= 0.36, which bundle the glyphs; the query is in-band, so it works when dtop runs over SSH (whereTERM_PROGRAMis lost andTERMis often rewritten). Falls back toTERM_PROGRAM/TERMwhen there is no reply. Under tmux the probe would name tmux, so it reads#{client_termtype}instead (tmux >= 3.3 runs XTVERSION against the outer terminal on attach); Unicode under screen. A reply later than the 1s timeout reaches the keyboard worker as keystrokes (only on links with a round trip over 1s); accepted rather than filtering inputall: Show all containers including stopped/exited (default: false)sort: Default sort field for container list ("uptime", "name", "cpu", "memory")sort_direction: Sort direction ("asc" or "desc") - if omitted, uses field's defaultcolumns: List of column IDs to show, in order (e.g., ["status", "name", "cpu", "memory"])hyperlinks: OSC 8 hyperlink mode for Dozzle links ("auto", "always", "never")stats_header: Show the system stats header (default: true)
See config.example.yaml for a complete example.
Preferences Persistence
Preferences (columns, sort field, sort direction, show-all, stats header) can be saved to the config file:
- Ctrl-S - Save current preferences to config file (creates
~/.config/dtop/config.yamlif no config exists) - Ctrl-R - Reset preferences to defaults (requires y/n confirmation)
Behavior:
- Saving preserves other config keys (hosts, icons, filters)
- Notifications show success/confirmation messages (auto-dismiss after 2 seconds)
- Reset affects in-memory state only; saved config is not deleted
Show All Containers
The --all / -a flag controls whether to show all containers or only running containers:
- CLI:
--allor-aflag (boolean, followsdocker ps -aconvention) - Config file:
all: true/falsein YAML
Behavior:
- By default, only running containers are shown
- The
--allflag enables showing all containers (including stopped, exited, paused) - The flag is one-way enable only: it can enable showing all containers but cannot disable it
- If config has
all: true, the CLI flag cannot override it back to false - Users can always toggle the view with the 'a' key in the UI
Examples:
dtop --all # Show all containers
dtop -a # Short version
dtop # Show running only (unless config has all: true)Note: This design matches Docker's docker ps -a behavior where the flag is a simple boolean enable.
Default Sort Field
The --sort / -s option sets the default sort field for the container list:
- CLI:
--sort <field>or-s <field> - Config file:
sort: <field>in YAML
Available sort fields:
uptime(oru) - Sort by container creation time (default, newest first)name(orn) - Sort by container name (alphabetically, ascending)cpu(orc) - Sort by CPU usage (highest first)memory(orm) - Sort by memory usage (highest first)
Behavior:
- Each field has a default sort direction (uptime/cpu/memory: descending, name: ascending)
- CLI takes precedence over config file
- Users can change the sort field and toggle direction in the UI with 's' (opens sort selector popup)
Examples:
dtop --sort name # Sort by name alphabetically
dtop -s cpu # Sort by CPU usage (highest first)
dtop --sort memory # Sort by memory usageContainer Filtering
The application supports Docker filters similar to docker ps --filter. Filters can be specified via:
- CLI:
--filteror-fflag (applies to all hosts) - Config file: Per-host
filterfield in YAML (host-specific filters)
CLI filters take precedence over config file filters.
Available filters (container listing):
id- Container ID (full or partial)name- Container name (supports partial matches)label- Label key or key=value pairstatus- Container state: created, restarting, running, removing, paused, exited, deadancestor- Image name/tag or descendantbefore- Created before container ID/namesince- Created after container ID/namevolume- Mounted volume or pathnetwork- Connected networkpublish/expose- Published/exposed portshealth- Healthcheck status: starting, healthy, unhealthy, noneexited- Exit codeisolation- Isolation type (Windows only)is-task- Service task containers (boolean)
Filter Logic:
- Multiple values for same filter = OR logic:
--filter status=running --filter status=paused - Different filter types = AND logic:
--filter status=running --filter name=nginx
Events API Compatibility:
Some filters only work with container listing, not the events stream. The application will log warnings for incompatible filters (e.g., status, ancestor, health) that won't apply to real-time event monitoring. Compatible filters for both listing and events include: label, network, volume.
Filters id and name are automatically mapped to the container filter for events API compatibility.
Architecture
The application follows an event-driven architecture with multiple async/threaded components communicating via a single mpsc channel (AppEvent). The architecture supports multi-host monitoring by spawning independent container managers for each Docker host.
Source Code Organization
The codebase is organized into logical modules:
src/
├── cli/ # CLI-related modules
│ ├── config.rs # Configuration file loading (YAML)
│ ├── connect.rs # Docker host connection and verification
│ ├── filters.rs # Docker filter parsing (--filter support)
│ └── update.rs # Self-update functionality
│
├── core/ # Core application logic
│ ├── app_state/ # Central state manager (modularized)
│ │ ├── mod.rs # AppState struct and main event dispatcher
│ │ ├── actions.rs # Action menu handling (start/stop/restart/remove)
│ │ ├── container_events.rs # Container lifecycle event handlers
│ │ ├── integrations.rs # Dozzle integration handlers
│ │ ├── log_view.rs # Log view event handlers
│ │ ├── navigation.rs # Selection and navigation handlers
│ │ ├── search.rs # Search mode and filtering handlers
│ │ └── sorting.rs # Container sorting logic
│ └── types.rs # Core types and events
│
├── docker/ # Docker-related functionality
│ ├── connection.rs # Container manager & Docker host abstraction
│ ├── details.rs # Inspect for the details popup
│ ├── logs.rs # Log streaming
│ ├── stats.rs # Stats streaming and calculation
│ └── actions.rs # Container actions (start/stop/restart/remove)
│
├── ui/ # UI rendering and input handling
│ ├── input.rs # Keyboard worker
│ ├── render.rs # Ratatui UI rendering
│ ├── container_list.rs # Container list table rendering
│ ├── action_menu.rs # Action menu popup rendering
│ ├── details.rs # Details popup rendering
│ ├── help.rs # Help popup rendering
│ ├── icons.rs # Icon sets (Unicode and Nerd Font)
│ └── ui_tests.rs # UI snapshot tests
│
├── lib.rs # Library root with module declarations
└── main.rs # Binary entry pointCore Components
- Main Event Loop (
main.rs::run_event_loop)- Receives events from all container managers via a shared channel
- Delegates state management to
AppStatestruct - Renders UI at 500ms intervals using Ratatui
- Uses throttling to wait for events or timeout, then drains all pending events
- AppState (
core/app_state/mod.rs::AppState)- Central state manager that handles all runtime data
- Maintains container state in
HashMap<ContainerKey, Container>whereContainerKeyis(host_id, container_id) - Manages view state (container list, log view, action menu, search mode)
- Handles log streaming, scrolling, and auto-scroll behavior
- Pre-sorts containers by host_id and selected sort field for efficient rendering
- Single source of truth for container data across all hosts
- Modular architecture: Event handlers are split across specialized modules:
actions.rs: Action menu navigation and executioncontainer_events.rs: Container lifecycle and stats updatesintegrations.rs: Dozzle integrationlog_view.rs: Log streaming and scrollingnavigation.rs: Selection and navigationsearch.rs: Search filtering and input handlingsorting.rs: Container sorting by multiple fields
- Container Manager (
docker/connection.rs::container_manager) - One per Docker host- Async task that manages Docker API interactions for a specific host
- Each manager operates independently with its own
DockerHostinstance - Fetches initial container list on startup
- Subscribes to Docker events (start/stop/die) for that host
- Spawns individual stats stream tasks per container
- Each container gets its own async task running
stream_container_stats - All events include the
host_idto identify their source - Reconnects automatically when the daemon goes away (e.g. a
dockerpackage upgrade restarts it and drops the socket). When the event stream breaks, the per-container stats tasks are aborted,HostDisconnectedis emitted so the UI shows a "reconnecting" banner, and the host is pinged with a capped backoff (1s → 5s) until it answers. On successHostReconnectedis emitted and the container list is re-fetched, which re-synchronizes state for that host.
- Stats Streaming (
docker/stats.rs::stream_container_stats)- One async task per container that streams real-time stats
- Uses exponential moving average (alpha=0.3) to smooth CPU, memory, network, and disk I/O stats
- Calculates network TX/RX rates in bytes per second
- Calculates disk read/write rates in bytes per second from
blkio_stats.io_service_bytes_recursive - CPU calculation: Delta between current and previous usage, normalized by system CPU delta and CPU count
- Memory calculation: Current usage divided by limit, expressed as percentage
- Note: Disk I/O stats may return zeros on some systems with certain cgroup configurations (see moby/moby#35352)
- Re-opens a broken stream with a capped backoff (1s → 5s) as long as the container
is still running, which is checked with an inspect before each retry so a stopped or
removed container does not spin against a 404. Stream errors are logged; swallowing
them left a container's row at zero for every metric for the rest of the session,
because nothing re-armed monitoring except a fresh
startevent. For the same reasoncontainer_managertreats a finished stats task as unmonitored — its handle stays inactive_containersuntil a stop/die/destroy event prunes it.
- Log Streaming (
docker/logs.rs::stream_container_logs)- Streams logs from a container in real-time
- Fetches last 100 lines on startup, then follows new logs
- Parses timestamps (RFC3339 format) and messages
- Uses
ansi-to-tuito parse ANSI escape codes for colored output - Preserves whitespace and formatting from original logs
- Sends each log line as
AppEvent::LogLineevent with pre-parsed Text
- Keyboard Worker (
ui/input.rs::keyboard_worker)- Blocking thread that polls keyboard input every 200ms
- Handles: 'q'/Ctrl-C (quit), Enter (show action menu/execute), Esc (exit view/cancel), Up/Down (navigate/scroll)
- Arrow keys: Right (view logs), Left (exit log view), '/' (search mode)
- Separate thread because crossterm's event polling is blocking
- Container Actions (
docker/actions.rs::execute_container_action)- Async execution of Docker container actions
- Supports: Start, Stop, Restart, Remove
- Stop/Restart use 10-second timeout before force kill
- Remove uses force option to remove even if running
- Sends progress events (InProgress, Success, Error) back to main event loop
Multi-Host Architecture
Host1 (local) → container_manager → AppEvent(host_id="local", ...) ┐
Host2 (server1) → container_manager → AppEvent(host_id="server1", ...)├→ Main Loop → UI
Host3 (server2) → container_manager → AppEvent(host_id="server2", ...)┘
Keyboard → keyboard_worker → AppEvent::Quit → Main Loop → ExitKey Design Points:
- Each host runs its own independent
container_managertask - All container managers share the same event channel (
mpsc::Sender<AppEvent>) - Every event includes a
host_idto identify which host it came from - Containers are uniquely identified by
ContainerKey { host_id, container_id } - The UI displays host information alongside container information
Event Types (core/types.rs::AppEvent)
Container-related events use structured types to identify containers across hosts:
InitialContainerList(HostId, Vec<Container>)- The authoritative container list for a host, sent on startup and again whenever the host reconnects (containers held for that host but missing from the list are dropped)ContainerCreated(Container)- New container started (host_id is in the Container struct)ContainerDestroyed(ContainerKey)- Container stopped/died (identified by host_id + container_id)ContainerStat(ContainerKey, ContainerStats)- Stats update (identified by host_id + container_id)ContainerHealthChanged(ContainerKey, HealthStatus)- Health status changed for a containerContainerDetails(ContainerKey, Result<ContainerDetails, String>)- Inspect result for the details popupQuit- User pressed 'q' or Ctrl-CResize- Terminal was resizedSelectPrevious- Move selection up (Up arrow in container list)SelectNext- Move selection down (Down arrow in container list)EnterPressed- User pressed Enter to show action menu or execute actionExitLogView- User pressed Left arrow to exit log viewShowLogView- User pressed Right arrow to view logsScrollUp- Scroll up in log view (Up arrow)ScrollDown- Scroll down in log view (Down arrow)LogLine(ContainerKey, LogEntry)- New log line received from streaming logsOpenDozzle- User pressed 'o' to open Dozzle for selected containerToggleHelp- User pressed '?' to toggle help popupKeyInput(KeyEvent)- User pressed 's' to open sort selector popupToggleShowAll- User pressed 'a' to toggle showing all containers (including stopped)CancelActionMenu- User pressed Esc to cancel action menu or exit viewsSelectActionUp- Navigate up in action menu (Up arrow)SelectActionDown- Navigate down in action menu (Down arrow)ActionInProgress(ContainerKey, ContainerAction)- Container action startedActionSuccess(ContainerKey, ContainerAction)- Container action completed successfullyActionError(ContainerKey, ContainerAction, String)- Container action failedEnterSearchMode- User pressed '/' to enter search modeSearchKeyEvent(KeyEvent)- Key event for search input (passed to tui-input)HostDisconnected(HostId)- Lost the connection to a previously connected host; it is being retried in the backgroundHostReconnected(HostId)- A disconnected host is reachable againHostInfo(HostId, HostInfo)- CPU count and total memory fromdocker info, sent on every (re)connectSystemStats(SystemStats)- A sample of the local machine's CPU/memory/swap/network/load/uptime (every 2s)RemoteSystemStats(HostId, SystemStats)- The same for an SSH host, read from its/proc(every 2s)
View States (core/types.rs::ViewState)
The application has these view states:
ContainerList- Main view showing all containers across all hostsLogView(ContainerKey)- Log viewer for a specific container with real-time streamingActionMenu(ContainerKey)- Action menu popup for a specific containerBulkActionMenu- Action menu popup for every marked containerDetails(ContainerKey)- Details popup for a container, opened from its action menuSearchMode- Search mode for filtering containers by name/IDColumnSelector- Column visibility and ordering popupSortSelector- Sort field selection popup
Container Data Model (core/types.rs::Container)
The Container struct holds both static metadata and runtime statistics:
pub struct Container {
pub id: String, // Truncated container ID (12 chars)
pub name: String, // Container name
pub state: ContainerState, // Running, Paused, Exited, etc.
pub health: Option<HealthStatus>, // Healthy, Unhealthy, Starting (None if no health check)
pub created: Option<DateTime<Utc>>, // Container creation timestamp
pub stats: ContainerStats, // CPU, memory, network, disk I/O stats (updated in real-time)
pub host_id: HostId, // Which Docker host this container belongs to
pub dozzle_url: Option<String>, // Dozzle URL for this container's host
}Container Identification:
- Containers are uniquely identified by
ContainerKey { host_id, container_id } - This allows tracking the same container ID across different hosts
- Container IDs are truncated to 12 characters (Docker API accepts partial IDs)
Docker Host Abstraction
The DockerHost struct (docker/connection.rs) encapsulates a Docker connection with its identifier and optional Dozzle URL:
pub struct DockerHost {
pub host_id: HostId,
pub docker: Docker,
pub dozzle_url: Option<String>,
}Host IDs are derived from the host specification:
"local"→ host_id ="local""ssh://user@host"→ host_id ="user@host""ssh://user@host:2222"→ host_id ="user@host"(port stripped)
Dozzle Integration:
- Dozzle URLs can be configured per-host in the config file
- Press 'o' in container list view to open Dozzle for the selected container
- Opens in browser at
{dozzle_url}/container/{container_id}format - Only works when not in an SSH session (detected via SSH_CLIENT/SSH_TTY/SSH_CONNECTION env vars)
- Container names are also rendered as clickable [OSC 8] hyperlinks to the same URL (see OSC 8 Hyperlinks below). Unlike the 'o' key, these do work over SSH, because the link is resolved by the user's local terminal emulator.
OSC 8 Hyperlinks
The hyperlinks config key / --hyperlinks flag controls whether container
names in the Name column render as clickable hyperlinks pointing at Dozzle.
Rows whose host has no dozzle URL are left as plain text.
auto(default) — enable only on terminals detected as supporting OSC 8 (src/ui/hyperlinks.rs::detect_osc8_support). Detection sniffsTERM_PROGRAM,TERM,WT_SESSION,ALACRITTY_WINDOW_ID,KONSOLE_VERSION, andVTE_VERSION. Multiplexers (TMUX,TERM=screen*/tmux*) are off under auto: tmux >= 3.4 needsset -ga terminal-features "*:hyperlinks"and that can't be probed without shelling out.always— render regardless of terminal. The escape hatch for tmux users who have configured passthrough.never— never render.
Implementation note: Table builds its cells from Text, which has nowhere
to carry an escape sequence, so the links can't be nested inside the table.
render_dozzle_links (src/ui/container_list.rs) draws Hyperlink widgets
(src/ui/hyperlinks.rs) on top of the already-rendered table. To find the Name
column's rect it replays the same Block::inner and Layout::horizontal calls
the table used, rather than hardcoding offsets, so the overlay can't drift from
the table's own layout. The row offset is read from TableState::offset()
after the table renders, since Table::render_rows is what writes the first
visible index back into the state. Labels are pre-clipped with clip_to_width so
a linked name truncates the same way an unlinked one does.
CellDiffOption::ForcedWidth is load-bearing. Buffer::diff decides how many
terminal columns a cell occupies from its symbol's display width, and an OSC 8
escape sequence measures dozens of columns wide while printing nothing. A cell
holding an escape sequence must therefore declare
CellDiffOption::ForcedWidth(<real width>), or the diff advances past that bogus
width and silently drops every following cell on the row — in practice the CPU
and Memory columns simply stop rendering.
For that reason Hyperlink writes the label as ordinary per-cell text and puts
escape sequences only on the first and last cells (opener prepended to the
first grapheme, terminator appended to the last), each tagged ForcedWidth. This
also keeps the row diffing, snapshotting, and copy-pasting as its plain text, and
keeps the backend's cursor-position tracking in sync (each cell still advances the
real cursor by exactly its declared width).
URL safety: the Dozzle URL is written verbatim inside the escape sequence, so
Hyperlink refuses to render a link whose URL contains control characters
(is_safe_url) — otherwise a dozzle: value from a shared or synced config file
could close the sequence early and inject arbitrary escapes into the terminal. It
fails closed (plain text, no link) rather than stripping, since rewriting a URL
could point the link somewhere the user never configured. Labels are safe by
construction: control characters are zero-width and get dropped during placement.
This is why the third-party hyperrat crate is not used: as of 0.1.3 it packs
the entire sequence plus the label into a single cell with CellDiffOption::None,
which triggers exactly the row-swallowing bug above.
test_hyperlink_does_not_swallow_following_columns and
test_hyperlink_reaches_the_real_backend_without_eating_the_row (ui/ui_tests.rs)
guard against regressing this; note that the second is the only test that exercises
the diff → real-backend byte path, where this class of bug is actually visible.
Configuration Loading
The Config struct (cli/config.rs) handles YAML configuration file loading:
- Searches multiple locations in priority order (see Configuration section above)
- Merges config file values with CLI arguments (CLI takes precedence)
- Uses
serde_norway(maintainedserde_yamlfork) for deserialization - Uses
dirscrate for home directory detection
Host Configuration Format:
The HostConfig struct contains:
host: String - The Docker connection string (required)dozzle: Option<String>- URL to Dozzle instance (optional)- Additional optional fields can be added in the future
All fields except host are optional and use #[serde(skip_serializing_if = "Option::is_none")].
The merge logic:
- If CLI hosts are explicitly provided (not default), they override config file
- If CLI uses default (
--host local) and config file has hosts, config file is used - If both are empty/default, defaults to
local - CLI hosts are converted to
HostConfigstructs withdozzle: None
Container Actions System
The application supports interactive container management through an action menu (ui/action_menu.rs and docker/actions.rs):
Action Flow:
- User presses Right arrow (→) in container list
- Action menu popup appears with available actions based on container state
- User selects action with Up/Down arrows and presses Enter
- Action is executed asynchronously via
execute_container_action() - Docker events automatically update container state in UI
Available Actions:
- Start: Available for Exited, Created, Dead containers
- Stop: Available for Running, Paused containers (10-second timeout)
- Restart: Available for Running containers (10-second timeout)
- Remove: Available for any state except Restarting/Removing (forced removal)
- Details: Available in every state (listed last, never in the bulk menu). Opens a popup
filled by one inspect (
docker/details.rs): image, command, created/started, last exit code and OOM kill, restart policy and count, ports, mounts, networks, label count. Environment variables are deliberately left out, since they often hold secrets
State-Based Availability:
- Running → Shell, Stop, Restart, Remove, Details
- Paused → Stop, Remove, Details
- Exited/Created/Dead → Start, Remove, Details
- Restarting/Removing → Details only
Bulk Actions (core/app_state/selection.rs):
- Marks live in
AppState::marked_containers; they are dropped with their container. Running an action unmarks only the containers it ran on; the title counts hidden marks - Bulk actions act only on marked containers that are listed (
marked_visible_keys), so a container hidden by search or the show-all toggle is never touched unseen - The bulk menu offers Start/Stop/Restart/Remove if any marked container supports them (no Shell), each labelled "(n of m)"; an action skips containers it doesn't suit
- Remove needs a second Enter (
armed_bulk_action), and only runs if it still hits the same containers (armed_bulk_targets, compared as a set since the list re-sorts). When container changes reshape an open menu,revalidate_action_menukeeps the cursor on the same action, or disarms if it is gone - Failed containers are re-marked, so retrying them is one Enter away
- Results are tracked per batch in
AppState::bulk_progress(batches can overlap) and reported as one summary toast: "Stopped 4 containers", or "Stopped 2 of 4 containers (2 failed, last:<reason>)". A one-container batch leaves its own error toast up instead. Containers on a disconnected host are skipped (and stay marked); a host disconnect drops batches waiting on it, and batches with no event for 60s are dropped (measured from the last event, since a large batch run 4 at a time can rightly take minutes). A container launched in a new batch is removed from any older batch - Each host runs at most 4 of a batch's actions at once (
BULK_CONCURRENCY_PER_HOST), so marking dozens of containers doesn't burst an SSH host's shared masters - An action menu left with no actions (its containers went away) closes itself
(
revalidate_action_menu)
Implementation Details:
- Actions spawn async tasks that don't block the UI
- Progress events sent back to main loop (InProgress, Success, Error)
- Container state updates happen automatically via Docker event stream
- Action menu closes immediately after execution for responsive UX
Docker Connection
The connect_docker() function (docker/connection.rs) handles these connection modes:
--host local: Resolves the endpoint via the Docker CLI's rules (see below), falling back to the OS default socket--host unix:///path/to/docker.sock: Connects to a specific Unix socket--host ssh://user@host[:port]: Connects via SSH (requires Bollard SSH feature)--host tcp://host:port: Connects via TCP to remote Docker daemon (unencrypted)--host tls://host:port: Connects via TLS to remote Docker daemon (encrypted, requires DOCKER_CERT_PATH)
Multiple --host arguments can be provided to monitor multiple Docker hosts simultaneously.
SSH Transport (docker/ssh.rs):
dtop uses its own SSH connector rather than Docker::connect_with_ssh, because bollard
drops the ssh:// scheme before resolving the destination, which breaks custom ports.
dtop holds one long-lived HTTP connection per container (the stats stream), plus the
event stream and any open log stream. To avoid one TCP connection and auth handshake per
container, connections are multiplexed over a pool of shared SSH control masters,
each carrying up to 8 channels (MAX_CHANNELS_PER_MASTER). 30 containers use 4
connections instead of 31.
- The cap of 8 stays under sshd's default
MaxSessionsof 10, which limits channels per connection. If a remote lowersMaxSessionsbelow 8, channel opens there will fail and the master gets evicted. - Master creation is serialized (the pool lock is held across the launch) so a startup
burst doesn't open many simultaneous handshakes — relevant to sshd's
MaxStartups, which throttles concurrent unauthenticated connections, and to fail2ban-style tooling that reads a connection burst as an attack. - A channel slot is tied to the connection's lifetime via a semaphore permit held in
SshStream; the slot returns to the pool when the connection closes. - If opening a channel fails (dead master after a network drop or daemon restart), that
master is evicted and the attempt retries once on a fresh one. This is what lets the
reconnect loop in
connection.rsheal an SSH host. - Masters are launched with
ServerAliveInterval=15sso a dead master surfaces as an error instead of hanging. Dropping the last reference runsssh -O exit, so masters are cleaned up on quit. SshRunner(returned byconnect_with_ssh, stored onDockerHost::ssh) runs short commands on the host over the same pool, borrowing one channel per command. It is what the remote system stats sampler uses. Usually that's a free slot on an existing master, but if every master is exactly full the sample launches one more (one extra connection over the counts above, relevant toMaxStartups/fail2ban). A timed-out command is dropped, and openssh'skill_on_dropcloses its channel.
Docker Context Resolution (docker/context.rs):
For the local host, dtop mirrors the Docker CLI's endpoint resolution so it works
out of the box with colima, Rancher Desktop, and similar tools. Resolution order:
DOCKER_HOSTenvironment variable (any scheme)DOCKER_CONTEXTenvironment variable naming a contextcurrentContextin~/.docker/config.json- The
defaultcontext (OS default socket)
For a named context, the endpoint is read from ~/.docker/contexts/meta/<sha256(name)>/meta.json.
The local host_id is preserved in the UI regardless of the resolved endpoint.
Note: TCP connections are unencrypted. Only use on trusted networks or with proper firewall rules. For encrypted connections, use TLS with certificates.
Stats Calculation
Stats are calculated in docker/stats.rs with exponential smoothing applied:
- CPU: Delta between current and previous CPU usage, normalized by system CPU delta and CPU count
- Memory: Current usage divided by limit, expressed as percentage
- Network: Calculates TX/RX rates by tracking byte deltas over time
- Disk I/O: Calculates read/write rates from
blkio_stats.io_service_bytes_recursive, aggregating "Read" and "Write" operations across all devices - Smoothing: Uses exponential moving average with alpha=0.3 to reduce noise and create smoother visualizations
UI Rendering
The UI (ui/render.rs) uses pre-allocated styles to avoid per-frame allocations.
Four View Modes:
- Container List View - Main table showing all containers
- System stats header (
ui/system_header.rs) between the title and the table. Toggled witht, orstats_header: falsein config (saved by Ctrl-S). Lines, in order:Systemline for the machine dtop runs on, sampled withsysinfobycore/system_stats.rs::system_stats_workeron its own thread (sysinfo refreshes block). Only spawned for thelocalhost; an explicitunix://socket may be forwarded from another machine, so it doesn't get one.- Then per host,
localfirst: aSystemline forssh://hosts and aDockerline. - Remote
Systemlines come fromdocker/remote_stats.rs, which runs onecat /proc/...script every 2s over the host's existing SSH masters (viaSshRunner, see SSH Transport). CPU and network rates are deltas between two reads. A host is dropped only after 3 reads in a row find no/proc(not Linux); every other failure (connection, killedcat, bad output) is retried forever with a 10s → 60s backoff.run_shtimes out after 10s and never evicts a master (a dead one is replaced by the Docker reconnect). Cleared onHostDisconnected. Dockerlines: CPU and memory summed over the host's running containers (AppState::docker_totals), measured againstNCPU/MemTotalfromdocker info(container CPU is per-core, so the sum is divided by the core count). Under Docker Desktop/colima those describe the VM, not the Mac.- Network is on
Systemlines only, and skips loopback,veth*,docker*andbr-*(is_virtual_interface): bridges and veths re-count container traffic. Docker lines have none because summed container counters include traffic between containers (counted twice, as TX and RX) that never reaches the machine's interfaces, so it could exceed the machine's own traffic. - Rendering formats each line once into one buffer and measures widths from
field ranges (
HeaderText); keep it that way,render_stats_header_allocationsguards the per-frame allocation cost. Both samplers idle while the header is hidden (AppState::header_visible, set viaset_show_system_header). - The CPU/MEM bars are sized so the widest line fills the terminal width (same width on every line, so they align). When space runs short, detail is shed in order: uptime, load, swap, bars, core count.
- Rows are reserved by growing the list block's top padding, so
block.inner()(and the hyperlink overlay) stay correct. Hidden on terminals too short to fit it plus 8 list rows.
- Dynamically shows/hides "Host" column (only shown when multiple hosts are connected)
- Displays: ID, Name, Host (conditional), CPU%, Memory%, PIDs, Net TX, Net RX, Disk R, Disk W, Uptime, Status
- PIDs and Disk I/O columns (Disk R, Disk W) are hidden by default - enable via column selector ('c')
- PIDs column shows the current number of processes/threads; when a PID limit is set it displays as
current/limit - Progress bars with percentage indicators for CPU and Memory
- Network and disk rates formatted as B/s, KB/s, MB/s, or GB/s
- Search bar at bottom when in SearchMode (filters containers as you type)
- System stats header (
- Log View - Full-screen log streaming for selected container
- Shows last 100 lines initially, then follows new logs
- Timestamps displayed in yellow with bold formatting
- Auto-scroll when at bottom, manual scroll preserves position
- Displays "[AUTO]" or "[MANUAL]" indicator in title
- Action Menu - Centered popup for container actions
- Shows available actions based on container state
- Actions: Start (stopped), Stop (running), Restart (running), Remove (any state)
- Displays container name and host in title
- Visual feedback with icons (▶ Start, ■ Stop, ↻ Restart, 🗑 Remove)
- Search Mode - Filter containers by name/ID
- Shows search input at bottom of container list
- Filters containers in real-time as you type
- Uses
tui-inputwidget for text editing - Escape or Enter to exit search mode
Color Coding for Metrics:
- Green: 0-50%
- Yellow: 50.1-80%
- Red: >80%
Sorting: Containers can be sorted by multiple fields:
- Default sort: Uptime (newest first, descending)
- Sort fields: Uptime, Name, CPU, Memory, PIDs, Net TX, Net RX, Disk Read, Disk Write, ID, Host, Status, Restarts
- Containers are sorted globally across all hosts by the selected field, with
host_idas tiebreaker - Press 's' to open sort selector popup
- In the sort popup, select a field to sort by; selecting the active field toggles direction
- Each field has a default direction: Name/ID/Host (ascending), Uptime/CPU/Memory/Network/Disk (descending)
- Sort state is tracked in
SortStatewith field and direction
Container Filtering:
- By default, only running containers are shown
- Press 'a' to toggle showing all containers (including stopped/exited containers)
- Filter state is tracked in
AppState::show_all_containers - Press '/' to enter search mode and filter by container name/ID
- Search filtering is case-insensitive and filters by both name and ID
- Filtered containers are automatically re-sorted after filtering
Health Status:
- Containers with health checks display their status: Healthy, Unhealthy, Starting
- Health status is parsed from Docker's health check information
- Status changes trigger UI updates via
ContainerHealthChangedevent
CI/CD Workflows
The project uses cargo-dist for building and releasing binaries across multiple platforms.
Release Workflow (.github/workflows/release.yml)
- Triggers on version tags (e.g.,
v0.1.0) - Uses
cargo-distfor cross-platform builds - Builds for multiple platforms: Linux (x86_64, ARM64), macOS (x86_64, ARM64)
- Automatically creates GitHub releases with generated changelogs
- Produces installers, archives, and checksums
- Three main jobs:
plan: Determines what needs to be builtbuild-local-artifacts: Builds platform-specific binariesbuild-global-artifacts: Creates installers and checksumshost: Uploads artifacts and creates GitHub release
Other Workflows
.github/workflows/pr-build.yml- Builds on pull requests.github/workflows/docker-build.yml- Builds Docker images for testing.github/workflows/docker-release.yml- Publishes Docker images on release.github/workflows/test.yml- Runs test suite
Build Features
The project supports optional features to control binary size and dependencies:
self-update Feature (enabled by default)
- Adds the
dtop updatesubcommand for self-updating the binary - Depends on
self_updatecrate with rustls (adds ~1.9MB to binary size) - Included in: Release binaries, cargo-dist builds, regular cargo builds
- Excluded from: Docker images (to minimize image size)
Usage:
# Build with self-update (default)
cargo build --release # Binary: ~3.8MB
# Build without self-update (smaller)
cargo build --release --no-default-features # Binary: ~1.9MBDocker Configuration:
The Dockerfile builds with --no-default-features to create minimal Docker images (~2.5MB vs ~4.7MB).
Since Docker containers are typically updated by pulling new images, the self-update feature isn't needed.
Changelog Management
The project uses git-cliff for automated changelog generation based on conventional commits.
Configuration
The changelog is configured in cliff.toml with the following settings:
- Follows conventional commit format (feat, fix, docs, chore, etc.)
- Groups commits by type (Features, Bug Fixes, Documentation, etc.)
- Filters out dependency update commits and release preparation commits
- Supports semantic versioning tags (v[0-9]+.[0-9]+.[0-9]+)
- Sorts commits within sections by oldest first
Conventional Commit Format
Commits should follow this format:
<type>(<scope>): <description>
[optional body]
[optional footer]Common types:
feat: New featurefix: Bug fixdocs: Documentation changesperf: Performance improvementsrefactor: Code refactoringstyle: Code style changestest: Test changeschore: Maintenance tasks (builds, CI, etc.)
Examples:
feat(update): add self-update command
fix(stats): correct CPU calculation for multi-core systems
docs: update installation instructions
chore(deps): update rust crate bollard to v0.19.4Generating Changelogs
# View latest release changelog
git-cliff --latest
# View unreleased changes
git-cliff --unreleased
# Generate changelog for a version range
git-cliff --tag v0.3.0..v0.3.6
# Write full changelog to file
git-cliff -o CHANGELOG.md
# Generate changelog and update for next version
git-cliff --unreleased --tag v0.4.0 -o CHANGELOG.mdIntegration with Cargo
The project integrates git-cliff with both cargo-release and cargo-dist:
cargo-dist integration (dist-workspace.toml):
generate-changelog = true- Automatically generates changelogs during releaseschangelog-backend = "git-cliff"- Uses git-cliff for changelog generation- The GitHub release workflow will automatically include the generated changelog
cargo-release integration (Cargo.toml):
pre-release-replacements- Automatically updates CHANGELOG.md duringcargo release- Adds new version entry when creating a release
Release Workflow:
- Make changes and commit using conventional commit format
- Run
cargo release <version>to create a new release - CHANGELOG.md is automatically updated with the new version
- Tag is created and pushed to GitHub
- GitHub Actions (via cargo-dist) builds binaries and creates a release with the changelog
The CHANGELOG.md file is automatically maintained and should be committed to the repository.
Key Dependencies
- Tokio: Async runtime for Docker API and event handling
- Bollard: Docker API client with SSH support (requires
sshfeature) - Ratatui: Terminal UI framework (v0.29)
- Crossterm: Cross-platform terminal manipulation (v0.29)
- Clap: CLI argument parsing with derive macros
- Serde/Serde_yaml: Configuration file deserialization
- Dirs: Cross-platform home directory detection
- Chrono: Date and time handling for log timestamps
- Futures-util: Stream utilities for async operations
- Open: Cross-platform URL opener for Dozzle integration
- Ansi-to-tui: ANSI escape code parsing for colored log output
- Timeago: Human-readable time formatting for container uptime
- Tui-input: Text input widget for search functionality
- Unicode-width: Display-width calculation for placing/clipping hyperlink labels
- Sysinfo: Local machine CPU/memory/swap/network/load for the stats header (
system+networkfeatures only)
Dev Dependencies
- Insta: Snapshot testing (use
cargo insta acceptto accept snapshots) - Mockall: Mock generation for testing
Performance Considerations
- UI refresh rate is throttled to 500ms to reduce CPU usage
- Event processing uses timeout-based throttling: waits for first event with timeout, then drains all pending
- Container stats streams run independently per container across all hosts
- Each host's container manager runs independently without blocking other hosts
- Keyboard polling is 200ms to balance responsiveness and CPU
- Styles are pre-allocated in
UiStyles::default()to avoid allocations during rendering - Container references (not clones) are used when building UI rows
- Container sorting is throttled to once every 3 seconds to avoid re-sorting on every render frame (stats updates are constant)
- User-initiated sort changes (field selection, search, toggle filters) bypass throttle for immediate response
- Container add/remove events also bypass throttle to maintain correctness
- This reduces sorting from ~2/sec to ~0.33/sec during normal operation (~83% reduction)
- Exponential smoothing (alpha=0.3) reduces noise in stats without heavy computation
- Failed host connections are logged but don't prevent other hosts from being monitored
- A host that drops after connecting is retried in the background with a capped backoff (1s → 5s), so a Docker daemon restart doesn't require restarting dtop
- Log streaming is only active when viewing a container's logs (stopped when exiting log view)
- Log text is formatted once when received and cached in
AppState::formatted_log_textto avoid re-parsing ANSI codes on every frame - ANSI parsing happens at log arrival time, not render time
User Interactions
Container List View:
↑/↓- Navigate between containersPageUp/PageDown(orCtrl-U/Ctrl-D) - Move selection up/down by one pageHome/End- Jump to first/last containerEnter- Open action menu for selected container (or the bulk action menu when containers are marked)Space- Mark/unmark the selected container for a bulk action (and move down)Ctrl-A- Mark every listed container, or unmark them if all are marked (under tmux/screen with the default prefix, press the prefix thenato send it through)p- Mark every listed container in the selected container's compose project on the same host, or unmark them if all are markedEsc- Clear marks→/l- View logs for selected containerqorCtrl-C- Quit applicationo- Open Dozzle for selected container (if configured and not in SSH session)?- Toggle help popup/- Enter search mode (filter containers)s- Open sort selector popup (Uptime, Name, CPU, Memory)c- Open column visibility selectort- Show/hide the system stats headera- Toggle showing all containers (including stopped containers)Ctrl-S- Save preferences (columns, sort, all, stats header) to config fileCtrl-R- Reset preferences to defaults (requires y/n confirmation)
Log View:
↑/↓- Scroll through logs manually←/horEsc- Return to container list?- Toggle help popup- Auto-scroll behavior: Automatically scrolls to bottom when new logs arrive (unless manually scrolled up)
Action Menu:
↑/↓- Navigate between available actionsEnter- Execute selected actionEsc- Cancel and return to container list- Available actions depend on container state (e.g., running containers can be stopped/restarted)
Search Mode:
- Type to filter containers by name or ID (case-insensitive)
Backspace/Delete- Edit search queryEnterorEsc- Exit search mode↑/↓- Navigate filtered results while searching
Testing Strategy
The codebase includes unit tests for:
- Stats calculation logic (
docker/stats.rs): CPU percentage, memory percentage, network rates, disk I/O rates, edge cases - UI color coding (
ui/render.rs): Threshold boundaries for green/yellow/red - Log parsing (
docker/logs.rs): Timestamp parsing, message extraction, edge cases - Config loading (
cli/config.rs): YAML deserialization, CLI merging, host configurations - UI snapshot tests (
ui/ui_tests.rs): Visual regression testing using insta
Run tests with cargo test or cargo insta test for snapshot tests.
Scope Discipline
Small first versions win. Building is cheap here; reviewing and maintaining is not.
- Agree the scope as a changelog before any code. For a new feature, write what ships (user-visible bullets) and an explicit "Not in this release" list, and get it approved. Anything not on the ship list is out of scope, however reasonable it looks.
- A plan is a menu, not a work order. Build phase 1 only. Later phases start after phase 1 has merged, unless the user asks for more.
- Reuse before you add. A new config key, CLI flag,
AppEvent,ViewState, key binding, column or popup needs a one-line reason why an existing one can't do it. Prefer extending an existing view (e.g. the action menu) to adding a new one. - Stop at ~1,000 lines. If a PR's non-test, non-snapshot diff passes ~1,000 lines, stop and check scope with the user before continuing.
- One feature, one PR. Unrelated work (docs, refactors, a fix spotted on the way) gets its own PR off main, never a layer in the same branch. Avoid stacks deeper than two; with a GitHub stack, merge from the bottom.
- Subagents and workflows get the out-of-scope list. Every implementation prompt includes "not in scope" and the rule: a deviation that adds behaviour is reported, not built.
- Review fixes fix; they don't extend. Addressing feedback must not add new mechanisms. If a fix needs one, raise it as a scope question.
- The automated review always finds something.
claude-reviewre-reviews every push and lists items even when it says LGTM. Fix the bugs it finds; answer nits, suggestions and "consider" items once in a reply, without pushing for them. A round with no bugs ends the review loop. Fixes made under review are the likeliest place for new bugs, so keep them minimal. - Audit size before review, not after. When a body of work is done, report its size per feature (code, tests, snapshots) next to its user impact, so cuts happen before review time is spent.
Pre-Commit Checklist
IMPORTANT: These checks MUST pass before creating any PR or commit.
Before committing or submitting a pull request, always run:
cargo fmt --check- Verify formatting (runcargo fmtto fix)cargo clippy- Ensure no warningscargo test- Ensure all tests passcargo insta accept- Accept any updated snapshots (if applicable)
Claude PR Review Guidelines
When reviewing pull requests for this repository, follow these guidelines:
- Keep feedback concise and actionable - Focus on what matters, not lengthy explanations
- Prioritize issues by severity:
- Security vulnerabilities (always report)
- Bugs and logic errors
- Performance issues
- API/interface issues
- Skip minor nitpicks - Don't comment on trivial style issues unless they significantly affect readability
- Be direct - Use short, clear sentences. Avoid filler phrases
- Group related feedback - Don't create separate comments for each small issue
- Limit comments to 3-5 items unless there are critical issues requiring more attention
Trending
Less talk lately.about the whole repository
Who's talking about amir20/dtop
- @lcheylus.bsky.social“dtop: a Terminal based Dashboard for Docker that monitors multiple Hosts in real-time ; CLI tool developed …”Bluesky · Oct 1, 2026 · 3 likes
- @stevensaus.faithcollapsing.com.ap.brid.gy“GitHub - amir20/dtop: Terminal dashboard for Docker monitoring across multiple hosts with Dozzle …”Bluesky · May 24, 2026 · 2 likes
- @matsuu.bsky.social“Docker環境における各コンテナのCPU, メモリー, トラフィックなどのリソース状況をtopコマンドのように逐次表示できるCLIツール。複数ホストも対応。Rust実装。”Bluesky · Apr 4, 2026 · 2 likes
- @stevensaus.com“GitHub - amir20/dtop: Terminal dashboard for Docker monitoring across multiple hosts with Dozzle …”Bluesky · May 24, 2026 · 1 likes
- @tom-doerr.bsky.social“https://github.com/amir20/dtop”Bluesky · Jan 10, 2026 · 1 likes
The biggest 5 of 54 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 54
Talked about on GitHub and Bluesky (54 times).
On GitHub · 49
- Oct 22, 2026Update dependency github:amir20/dtop to v0.10.4 — hay-kot/dotfiles0 reactions
- Oct 20, 2026dtop: 0.9.4 -> 0.10.4 — NixOS/nixpkgs0 reactions · 3 comments
- Oct 19, 2026Update dependency github:amir20/dtop to v0.10.1 — hay-kot/dotfiles0 reactions
- Oct 17, 2026dtop 0.10.4 — Homebrew/homebrew-core0 reactions · 1 comment
- Oct 16, 2026dtop 0.10.3 — Homebrew/homebrew-core0 reactions · 1 comment
44 more on GitHub
- Oct 12, 2026dtop 0.10.1 — Homebrew/homebrew-core0 reactions · 1 comment
- Oct 11, 2026dtop 0.10.0 — Homebrew/homebrew-core0 reactions · 1 comment
- Oct 3, 2026Update dependency github:amir20/dtop to v0.9.4 — hay-kot/dotfiles0 reactions
- Sep 28, 2026dtop: 0.9.3 -> 0.9.4 — NixOS/nixpkgs0 reactions
- Sep 26, 2026dtop 0.9.4 — Homebrew/homebrew-core0 reactions · 1 comment
- Sep 18, 2026dtop: 0.9.2 -> 0.9.3 — NixOS/nixpkgs0 reactions
- Sep 17, 2026Update dependency github:amir20/dtop to v0.9.3 — hay-kot/dotfiles0 reactions
- Sep 10, 2026dtop 0.9.3 — Homebrew/homebrew-core0 reactions · 1 comment
- Sep 8, 2026dtop: 0.9.0 -> 0.9.2 — NixOS/nixpkgs0 reactions
- Sep 7, 2026dtop 0.9.2 — Homebrew/homebrew-core0 reactions · 1 comment
- Sep 6, 2026Update dependency github:amir20/dtop to v0.9.2 — hay-kot/dotfiles0 reactions
- Sep 1, 2026Update dependency github:amir20/dtop to v0.9.0 — hay-kot/dotfiles0 reactions
- Aug 30, 2026dtop 0.9.1 — Homebrew/homebrew-core0 reactions · 1 comment
- Aug 29, 2026dtop: 0.8.0 -> 0.9.0 — NixOS/nixpkgs0 reactions · 2 comments
- Aug 25, 2026dtop 0.9.0 — Homebrew/homebrew-core0 reactions · 1 comment
- Aug 14, 2026Update dependency github:amir20/dtop to v0.8.0 — hay-kot/dotfiles0 reactions
- Aug 12, 2026Update dependency github:amir20/dtop to v0.7.12 — hay-kot/dotfiles0 reactions
- Aug 10, 2026dtop: 0.7.11 -> 0.8.0 — NixOS/nixpkgs0 reactions · 2 comments
- Aug 7, 2026dtop 0.8.0 — Homebrew/homebrew-core0 reactions · 1 comment
- Aug 4, 2026dtop 0.7.12 — Homebrew/homebrew-core0 reactions · 1 comment
- Aug 1, 2026dtop: 0.7.9 -> 0.7.11 — NixOS/nixpkgs0 reactions
- Jul 31, 2026Update dependency github:amir20/dtop to v0.7.11 — hay-kot/dotfiles0 reactions
- Jul 27, 2026dtop 0.7.11 — Homebrew/homebrew-core0 reactions · 1 comment
- Jul 23, 2026dtop 0.7.10 — Homebrew/homebrew-core0 reactions · 1 comment
- Jul 17, 2026Update dependency github:amir20/dtop to v0.7.9 — hay-kot/dotfiles0 reactions
- Jul 15, 2026Update dependency github:amir20/dtop to v0.7.8 — hay-kot/dotfiles0 reactions
- Jul 14, 2026dtop: 0.7.7 -> 0.7.9 — NixOS/nixpkgs0 reactions · 2 comments
- Jul 10, 2026dtop 0.7.9 — Homebrew/homebrew-core0 reactions · 1 comment
- Jul 8, 2026dtop 0.7.8 — Homebrew/homebrew-core0 reactions · 1 comment
- Jun 15, 2026Update dependency github:amir20/dtop to v0.7.7 — hay-kot/dotfiles0 reactions
- Jun 12, 2026Update dependency github:amir20/dtop to v0.7.7 — hay-kot/dotfiles0 reactions
- Jun 9, 2026dtop: 0.7.6 -> 0.7.7 — NixOS/nixpkgs0 reactions · 2 comments
- Jun 5, 2026dtop 0.7.7 — huncrys/homebrew-tap0 reactions
- Jun 5, 2026dtop 0.7.7 — Homebrew/homebrew-core0 reactions · 1 comment
- May 5, 2026dtop 0.7.6 — huncrys/homebrew-tap0 reactions
- May 5, 2026dtop 0.7.6 — Homebrew/homebrew-core0 reactions · 1 comment
- May 5, 2026dtop 0.7.5 — huncrys/homebrew-tap0 reactions
- May 5, 2026dtop 0.7.5 — Homebrew/homebrew-core0 reactions · 1 comment
- May 4, 2026Update dependency github:amir20/dtop to v0.7.6 — hay-kot/dotfiles0 reactions
- May 4, 2026dtop 0.7.4 — huncrys/homebrew-tap0 reactions
- May 4, 2026dtop 0.7.4 — Homebrew/homebrew-core0 reactions · 1 comment
- Apr 11, 2026dtop 0.7.3 — huncrys/homebrew-tap0 reactions
- Mar 26, 2026dtop 0.7.2 — huncrys/homebrew-tap0 reactions
- Mar 26, 2026dtop 0.7.2 — Homebrew/homebrew-core0 reactions · 1 comment
On Bluesky · 5
- Oct 1, 2026dtop: a Terminal based Dashboard for Docker that monitors multiple Hosts in real-time ; CLI tool developed with Rust programming language… — @lcheylus.bsky.social3 likes and reposts
- May 24, 2026GitHub - amir20/dtop: Terminal dashboard for Docker monitoring across multiple hosts with Dozzle integration. · GitHub Terminal dashboard… — @stevensaus.faithcollapsing.com.ap.brid.gy2 likes and reposts · 1 reply
- Apr 4, 2026Docker環境における各コンテナのCPU, メモリー, トラフィックなどのリソース状況をtopコマンドのように逐次表示できるCLIツール。複数ホストも対応。Rust実装。 — @matsuu.bsky.social2 likes and reposts
- May 24, 2026GitHub - amir20/dtop: Terminal dashboard for Docker monitoring across multiple hosts with Dozzle integration. · GitHub Terminal dashboard… — @stevensaus.com1 likes and repost
- Jan 10, 2026https://github.com/amir20/dtop — @tom-doerr.bsky.social1 likes and repost
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

