Features
- Container detection - Queries Docker and Podman daemons to discover running containers and their published port bindings.
- Port-to-container mapping - Resolves which container owns a given host
(ip, port, protocol)tuple, with wildcard and proxy-fallback matching. - Container lifecycle control - Stop or kill containers by ID through the daemon API (graceful SIGTERM or immediate SIGKILL).
- Multi-transport support - Connects via Unix domain sockets, Windows named
pipes, or TCP (
DOCKER_HOST), with automatic discovery of socket paths. - Rootless Podman support - Resolves rootless Podman containers on Linux by reading overlay storage metadata and matching network namespace paths.
- Background detection - Spawns detection on a background thread so callers can do other work (socket enumeration, process lookup) concurrently.
- Minimal dependencies - Only
serde,serde_json,httparse, andlogat runtime. No async runtime, notokio, nohyper. - Cross-platform - Works on Linux (x86-64) and Windows (x86-64).
Quick Start
Add nanodock to your Cargo.toml:
[]
= "0.1"
Detect containers and map ports
Two detection paths are available:
Best-effort path (background thread, never errors):
use ;
Strict path (synchronous, returns errors):
use detect_containers;
Look up which container owns a socket
use ;
use ;
Stop a container
use ;
How It Works
nanodock communicates directly with the Docker/Podman daemon using the
/containers/json REST API endpoint over local transports:
┌─────────────┐ HTTP/1.0 GET /containers/json
│ nanodock │ ──────────────────────────────────────┐
│ (your app) │ │
└─────────────┘ ▼
┌─────────────────┐
Unix socket (/var/run/docker.sock) ───► │ │
Named pipe (\\.\pipe\docker_engine) ──► │ Docker/Podman │
TCP (DOCKER_HOST=tcp://...) ───► │ Daemon │
│ │
└─────────────────┘
Transport Discovery Order
DOCKER_HOSTenvironment variable - If set, the specified transport (tcp://, unix://, npipe://) is used first.- Platform-native sockets - On Linux, well-known Unix socket paths are probed (rootful Docker, rootless Docker, Podman). On Windows, named pipes for Docker Desktop and Podman Machine are tried.
- Rootless Podman overlay (Linux only) - For containers managed by rootless
Podman, nanodock reads the overlay storage metadata to resolve container
names from network namespace paths. This handles the case where
rootlessportis the process holding the socket instead of the container itself.
Supported Daemon Paths
| Platform | Transport | Path |
|---|---|---|
| Linux | Unix socket | /var/run/docker.sock |
| Linux | Unix socket | /run/user/{uid}/docker.sock |
| Linux | Unix socket | $HOME/.docker/desktop/docker.sock |
| Linux | Unix socket | $HOME/.docker/run/docker.sock |
| Linux | Unix socket | /run/user/{uid}/podman/podman.sock |
| Linux | Unix socket | /run/podman/podman.sock |
| Windows | Named pipe | \\.\pipe\docker_engine |
| Windows | Named pipe | \\.\pipe\podman-machine-default |
| Both | TCP | DOCKER_HOST=tcp://host:port |
API Reference
Full API documentation is available on docs.rs.
Core Types
| Type | Description |
|---|---|
Protocol |
Network protocol enum (Tcp, Udp) |
ContainerInfo |
Container metadata (id, name, image) |
ContainerPortMap |
HashMap mapping (ip, port, protocol) to ContainerInfo |
PublishedContainerMatch |
Result of looking up a socket in the port map |
StopOutcome |
Result of a stop/kill request |
DetectionHandle |
Handle for in-progress background detection |
Error |
Error type for strict-path detection failures |
Core Functions
| Function | Description |
|---|---|
detect_containers(home) |
Synchronous detection, returns Result<Map, Error> |
start_detection(home) |
Spawn background daemon query, returns handle |
await_detection(handle) |
Block for results (3s timeout), returns map |
lookup_published_container() |
Match a socket against the port map |
stop_container(id, force, home) |
Stop or kill a container by ID |
parse_containers_json(body) |
Parse raw /containers/json response |
parse_containers_json_strict() |
Strict parse that returns Result on invalid JSON |
Linux-only Functions
| Function | Description |
|---|---|
is_podman_rootlessport_process(name) |
Check if a process name is rootlessport |
lookup_rootless_podman_container() |
Resolve container from rootlessport PIDs |
RootlessPodmanResolver |
Cached resolver for rootless Podman |
Architecture
src/
├── lib.rs — Public API, detection orchestration, port matching
├── api.rs — JSON response parsing, container name resolution
├── http.rs — Minimal HTTP/1.0 response parser (via httparse)
├── ipc.rs — OS-specific transport (Unix socket, named pipe, TCP)
└── podman.rs — Rootless Podman resolver via overlay metadata (Linux)
Module Boundaries
lib.rsowns the public API surface, detection orchestration, and port-to-container matching logic. All public types are defined here.api.rsowns JSON response parsing. It converts raw daemon responses intoContainerPortMapentries.http.rsowns HTTP protocol handling. It formats requests and parses responses usinghttparse. No Docker-specific logic lives here.ipc.rsowns OS-specific transport code. Unix sockets, Windows named pipes, TCP connections, andDOCKER_HOSTparsing all live here.podman.rsowns rootless Podman resolution. It reads overlay storage metadata and OCI runtime configs to match network namespace paths to container names.
Building
# Debug build
# Run tests
# Compile benchmarks
# Run benchmarks on Linux with valgrind and gungraun-runner installed
# Check formatting
# Run clippy (all+pedantic+nursery at deny level)
# Build documentation
# Dependency audit (requires cargo-deny)
Quality Gates
All of the following must pass before merging:
| Gate | Command | Purpose |
|---|---|---|
| 1 | cargo fmt --check |
Consistent formatting |
| 2 | cargo clippy |
Zero lint warnings |
| 3 | cargo test --lib --tests && cargo test --doc |
All tests pass |
| 4 | cargo bench --no-run |
Benchmarks compile |
| 5 | cargo build |
Library compiles |
| 6 | cargo doc --no-deps |
Documentation builds |
| 7 | cargo deny check |
No vulnerable/banned deps |
Instruction Benchmarks
nanodock ships a Gungraun benchmark suite for the two hot paths most likely to
regress in real use: parsing daemon /containers/json payloads and matching
host sockets back to published container bindings.
Unlike Criterion, Gungraun measures instruction counts and related Callgrind
metrics instead of wall-clock time. The benchmark output is written under
target/gungraun/.
Important constraints from the upstream Gungraun docs:
- benchmark execution requires Linux plus Valgrind
- benchmark execution also requires a version-matched
gungraun-runnerbinary - Windows can compile the benchmark harness with
cargo bench --no-run, but it cannot execute the benchmarks
To install the benchmark runtime on Linux:
To create and compare a named baseline locally:
Pull requests run a Linux benchmark job that:
- saves a merge-base baseline from
main - compares the PR head against that baseline using instruction deltas (
Ir) - fails the job if any benchmark regresses by more than 1%
- uploads raw console output plus the generated
target/gungraun/reports as artifacts
If you update the gungraun crate version, update the installed
gungraun-runner version in CI and local setup to match.
Git Hooks
Install local quality gates (runs fmt, clippy, and tests before each commit):
Windows (PowerShell):
.\scripts\install-hooks.ps1
Linux / macOS:
Minimum Supported Rust Version
nanodock requires the latest stable Rust toolchain (currently 1.93+) and uses edition 2024 features.
Dependencies
nanodock keeps its dependency tree intentionally small:
| Crate | Purpose |
|---|---|
serde |
Container metadata serialization |
serde_json |
JSON response parsing |
httparse |
HTTP/1.x response header parsing |
log |
Debug diagnostics via log facade |
libc |
Unix-only: getuid() for socket paths |
No async runtime. No TLS. No network client libraries.
Contributing
See CONTRIBUTING.md for development setup, coding standards, commit message format, and the full quality gate reference.
License
Licensed under the MIT License.
Copyright (c) 2026 Ehsan Khan