Skip to content

Build1 publisher2 min readPublished

Portop resolves a busy port by matching socket inodes against every process's open files

The Go TUI reads the kernel socket tables itself and derives systemd and Docker ownership from cgroups. The table it shows you is exactly as complete as the permissions of the user who launched it.

The Engineer · Build desk

Photograph accompanying Portop resolves a busy port by matching socket inodes against every process's open files
Photo: dev.to

What happened

  • Portop is an open source Go TUI that shows a live table of listening ports and established connections, with search, filter, process inspection, browser open, copy and kill available from the same screen.
  • Systemd and Docker associations are derived primarily from cgroups, and container names come from read-only calls to the Docker Engine Unix socket when one is available.
  • A --compose flag compares the published ports in a Docker Compose configuration against what is actually listening, separating expected ports, conflicts, unused ports and dynamic mappings.
  • Running --save-baseline stores the current listening ports, and --diff later reports which ones appeared or disappeared, returning a nonzero exit code when it finds changes.
  • Linux builds cover amd64, arm64, armv7 and armv6 with .deb and .rpm packages, and macOS support for Intel and Apple Silicon arrived later.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Because the tool inherits the launching user's permissions, an unprivileged run on a multi-tenant host omits the rows that belong to other users, and the port conflict you did not cause still ends in a sudo.
  • decision Operators who keep the Docker Engine socket closed can still get container association from cgroups; opening it read-only buys the container names and nothing else.
  • capability One binary now answers both the interactive question and the scheduled one, so a port-drift check can run from a systemd timer without a second script wrapping ss and diffing its output.

The join costs a search. Portop takes the socket inode out of /proc/net/tcp, /proc/net/tcp6, /proc/net/udp and /proc/net/udp6, then walks /proc/<pid>/fd until it finds the process holding a descriptor on that inode [3]. The work scales with the number of open descriptors on the box, not with the number of ports you asked about. What comes back is one row tying the socket to its process [2], with a detail view behind Enter carrying the command line, executable, working directory, user, memory usage, thread count and start time [10].

Permissions bound all of it. "If your user cannot inspect another process, Portop cannot magically bypass that," the author wrote on dev.to [6]. Running it under sudo gives the additional visibility [7]. There is no helper process holding privilege on your behalf either: "There is no privileged background daemon running just to make Portop work," the post said [5]. On a shared host, the unprivileged view is missing rows in exactly the place you are looking.

The five commands in the opening routine [20] collapse into one under conditions the post states about its author's own machines: "I work with Linux systems where having a lot of services and ports in use is normal," he wrote [8]. For the saving to transfer, your services need to sit under systemd or Docker cgroups, because that is where the unit and container association is derived from [4], and you need to be root or the owner of the process [7]. On a box running two services, ss -tlnp is already the whole investigation [1].

The Compose check is the part I would wire into something. Past the port comparison, it will also verify that the listener belongs to the expected Compose service, and that step needs Docker information to be available [12]. The author did not plan the feature; he added it after someone asked, following the initial release, whether Portop could tell you if the ports configured in a Compose project were actually in use [19]. It takes --json [13].

The headless path is the diff, and the post names scripts, cron and systemd timers as the consumers [16]. The interactive path is --watch-new, which watches for newly opened listening ports and can fire a desktop notification [15]. That one assumes somebody is sitting in front of the desktop.

portop --json prints the current state for jq or another script [17]. The author's split is that the TUI suits manual investigation while JSON suits handing the same information to another tool [22]. The post describes Portop as open source and does not name a license [21].

What to watch

  • Whether the macOS builds can derive the same service and container association, given that the Linux join comes out of cgroups.
  • Whether the JSON output gets a documented, stable schema before teams wire --diff into cron jobs.
  • Whether container-name resolution stays within read-only calls to the Docker Engine socket as the feature set grows.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories