Portain

Something has port 3000. Find out what.

Portain is one native window over the two things you actually want to see on a dev machine: every Docker container, grouped the way Compose grouped them, and every process sitting on a TCP port. Stop, restart, kill — then get back to work.

Download for Mac

Free · MIT · macOS 14+ · universal binary

Search containers, images, ports
RUNNING7
STOPPED6
northwind-postgresUp 2 days (healthy)

PORTS

5432 → 5432/tcplocalhost:5432

DETAILS

Imagepostgres:16-alpine
Container IDb7fefea0f8f9
Projectnorthwind
Commanddocker-entrypoint.sh postgres
Created2 days ago
CPU0.11%
Memory48.2MiB / 7.65GiB
Size241kB (virtual 272MB)

That window is drawn, not screenshotted — click the sidebar, pick a row, fold a project. The containers and the ports are invented; the layout, the columns and the actions are the app's.

Read-only by design.

It shells out to docker and lsof and reads what comes back. It never installs a daemon, never registers a helper, and never changes anything you did not click.

Not an Electron window.

Swift and SwiftUI, a universal binary, real sidebar materials and real SF Symbols. It launches like a Mac app because it is one.

Not an orchestrator.

No compose up, no builds, no image pulls, no volume editor. Seeing what is running and acting on it is the whole product.

Containers

Your stacks, shaped like your stacks.

docker ps hands you a flat list where a four-service project reads as four unrelated rows. Portain puts the Running ones above the Stopped ones and nests each one under its Compose project, as a Finder-style folder — expanded when it is up, collapsed when it is not.

01

One line per container

Status dot, name, image, published ports. The service name shows inside a folder, so you read “postgres”, not “northwind-postgres”.

02

Hover to act

The port chips swap for a start or stop button under the pointer — the row keeps its exact footprint, so nothing jumps.

03

A folder acts as a unit

Hover a project header and the badge becomes one button that starts or stops every service in it.

04

Half-started is obvious

A project with some services up and some down appears in both sections, which is exactly when you need to notice.

The detail pane is the control panel

Start, Stop, Restart, Kill, Logs and Remove, with the container's ports table and live CPU and memory from docker stats.

Ports you can read

A published mapping shows as 8080 → 3000/tcp with the URL beside it. An unpublished one is marked Internal and locked.

Logs without leaving

Hit Logs and the last two hundred lines open in a sheet over the window you were already in — monospaced, selectable, closed with Done.

Ports

The answer to “address already in use”.

Every listening TCP port on the machine in one native table: the port, the process holding it, its PID, whether it is bound to localhost or to every interface, and who owns it. Select one and free it — Terminate sends SIGTERM, Force Kill sends SIGKILL, and both ask first.

01

Docker ports resolve

A port published by a container shows the container's name rather than com.docker.backend, with a docker badge and a link across to it.

02

Scope at a glance

A padlock means 127.0.0.1 only. A globe means anything on your network can reach it — which is worth knowing before you leave the café.

03

Select several

Multi-select the table and terminate or force-kill the lot, or copy every PID in one go.

04

Dual-stack collapses

lsof reports one row per descriptor. Portain folds a dual-stack bind into a single row labelled TCP46 rather than showing it twice.

5000

It is AirPlay Receiver. It is always AirPlay Receiver.

macOS listens on 5000 and 7000 out of the box, which is why your dev server will not bind and why no amount of restarting fixes it. Portain names the process instead of leaving you to guess — that one is ControlCenter, and the fix is a checkbox in System Settings, not a kill.

How it works

Six commands you could have typed yourself.

There is no API client, no socket, no privileged helper and nothing running when the window is closed. Portain runs the commands below, parses the output and draws it. That is the entire mechanism, and it is why an audit of this app is a short read.

It also means the failure modes are ones you already understand. Docker not installed, daemon not up, a process owned by root that will not take a signal from you — each one says so plainly instead of spinning.

Containersdocker ps -a --format '{{json .}}'
Live statsdocker stats --no-stream
Container actionsdocker start / stop / restart / kill / rm
Logsdocker logs --tail 200
Portslsof -nP -iTCP -sTCP:LISTEN -F
Free a portkill -TERM · kill -KILL

Menu bar

Or never open the window at all.

A companion that lives in the status bar with no dock icon, lists every active port, and kills the one you click — after asking. Docker's ports are listed underneath and deliberately inert, because the thing to stop is the container, not the proxy.

  • Refreshes itself every five seconds
  • One click to kill, with a confirmation
  • Docker ports shown for reference, not killable
  • Launches the full window when you want it

Built from source for now — swift run PortainMenuBar. The released .app bundles the main window only.

Portain Menu Bar
Open Portain⌘O
Active Ports (16)
:631 — launchd
:3000 — node
:5000 — ControlCenter
:5173 — node
:7000 — ControlCenter
:8000 — python3
+4 more
Docker Ports
:5432 — Docker
:5433 — Docker
:5434 — Docker
+3 more
Refresh Now⌘R
Quit Menu Bar⌘Q

Terminal companion

The same two views, over SSH.

bunx portain gives you the containers and the ports in a terminal, with the same grouping, the same actions and the same confirmations. Handy on a box with no screen, and handier still when the window would be a context switch.

portain — containers132×28


   PORTAIN  [1 CONTAINERS]  2 PORTS

 ┌─Containers · 7 up / 13──────────────────────────────────────────────────────────────────────┐ ┌─Details────────────────────────┐
 ▾ RUNNING  7                                                                               │ │                                │
 ▾ northwind  (4)                                                                          │ │ helio-postgres                 
 northwind-postgres  postgres:16-alpine  0.0.0.0:5432->5432/tcp                       │ │                                │
 northwind-redis  redis:7-alpine  0.0.0.0:6379->6379/tcp                              │ │ Up 6 hours (healthy)           
 northwind-api  northwind-api:latest  0.0.0.0:8080->3000/tcp                          │ │                                │
 northwind-worker  northwind-api:latest  │ │ image  postgres:17-alpine      
 ▾ helio  (2)                                                                              │ │ id      89ba0c44aaf7           
 helio-postgres  postgres:17-alpine  0.0.0.0:5433->5432/tcp                           │ │ ports   0.0.0.0:5433->5432/tcp 
 helio-mailpit  axllent/mailpit:latest  0.0.0.0:8025->8025/tcp, 1025/…                │ │                                │
 ▾ riogrande  (1)                                                                          │ │                                │
 riogrande-postgres  postgres:16-alpine  0.0.0.0:5434->5432/tcp                        │ │                                │
 ▾ STOPPED  6                                                                                │ │                                │
 ▾ turnstile  (3)                                                                           │ │                                │
 ● turnstile-payload  turnstile-cms:latest   │ │                                │
 ● turnstile-postgres  postgres:16-alpine   │ │                                │
 ● turnstile-minio  minio/minio:latest   │ │                                │
 ▾ Standalone  (3)                                                                          │ │                                │
 ● meilisearch  getmeili/meilisearch:…   │ │                                │
 ● mailhog  mailhog/mailhog:latest   │ │                                │
 └─────────────────────────────────────────────────────────────────────────────────────────────┘ └────────────────────────────────┘
   Updated 2:33:42 PM  s start/stop · r restart · d remove · f fold · Shift+f section                                       v1.1.2


Running above stopped, each still folded into its Compose project. f folds the project under the cursor, ⇧F the whole section.
portain — ports132×28


   PORTAIN   1 CONTAINERS  [2 PORTS]

 ┌─Listening ports · 16────────────────────────────────────────────────────────────────────────┐ ┌─Details────────────────────────┐
 ❯ 631     launchd               415      ::1                 root                           │ │                                │
 3000    node                  31804    127.0.0.1           dev                            │ │ :631                           
 5000    ControlCenter         612      *                   dev                            │ │                                │
 5173    node                  48120    127.0.0.1           dev                            │ │ launchd                        
 5432    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 5433    com.docker.backend    2184     *                   DOCKER                         │ │ pid     415                    
 5434    com.docker.backend    2184     *                   DOCKER                         │ │ user    root                   
 6379    com.docker.backend    2184     *                   DOCKER                         │ │ addr    ::1                    
 7000    ControlCenter         612      *                   dev                            │ │ type    TCP6                   
 8000    python3               52011    *                   dev                            │ │                                │
 8025    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 8080    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 9229    node                  48120    127.0.0.1           dev                            │ │                                │
 11434   ollama                60432    127.0.0.1           dev                            │ │                                │
 24678   node                  31804    127.0.0.1           dev                            │ │                                │
 49152   rapportd              7714     *                   dev                            │ │                                │
 │                                                                                             │ │                                │
 │                                                                                             │ │                                │
 │                                                                                             │ │                                │
 └─────────────────────────────────────────────────────────────────────────────────────────────┘ └────────────────────────────────┘
   Updated 2:33:52 PM  x terminate · Shift+x force kill                                                                     v1.1.2


The same lsof read, sorted by port. Docker's listeners are labelled rather than attributed to your account.
portain — terminate132×28


   PORTAIN   1 CONTAINERS  [2 PORTS]

 ┌─Listening ports · 16────────────────────────────────────────────────────────────────────────┐ ┌─Details────────────────────────┐
 631     launchd               415      ::1                 root                           │ │                                │
 3000    node                  31804    127.0.0.1           dev                            │ │ :11434                         
 5000    ControlCenter         612      *                   dev                            │ │                                │
 5173    node                  48120    127.0.0.1           dev                            │ │ ollama                         
 5432    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 5433    com.docker.backend    2184     *                   DOCKER                         │ │ pid     60432                  
 5434    com.docker.backend    2184     *                   DOCKER                         │ │ user    dev                    
 6379    com.docker.backend    2184     *                   DOCKER                         │ │ addr    127.0.0.1              
 7000    ControlCenter         612      *                   dev                            │ │ type    TCP4                   
 8000    python3               52011    *                   dev                            │ │                                │
 8025    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 8080    com.docker.backend    2184     *                   DOCKER                         │ │                                │
 9229    node                  48120    127.0.0.1           dev                            │ │                                │
 ❯ 11434   ollama                60432    127.0.0.1           dev                            │ │                                │
 24678   node                  31804    127.0.0.1           dev                            │ │                                │
 49152   rapportd              7714     *                   dev                            │ │                                │
 │                                                                                             │ │                                │
 │                                                                                             │ │                                │
 │                                                                                             │ │                                │
 └─────────────────────────────────────────────────────────────────────────────────────────────┘ └────────────────────────────────┘
   CONFIRM?  y or Enter / any key cancel  x terminate · Shift+x force kill                                                  v1.1.2


x — nothing destructive happens without a y. Any other key cancels.

Those three are real frames: the TUI was run inside a pseudo-terminal against a stubbed docker and lsof, and every cell it painted was recorded with the colour it painted it in. The machine it is looking at does not exist.

Swap between Containers and PortsTab
Move the cursorjk
Start or stop the container under the cursors
Restart itr
Remove itd
Fold the compose project under the cursorf
Fold the whole Running or Stopped sectionF
Terminate the process on this portx
Force kill itX
Quitq

Fine print

What it will not do.

Better to read this now than to find out at the download.

macOS warns you on first launch

Notarising needs a paid Apple Developer ID, so the released build is not notarised and macOS blocks it once. System Settings ▸ Privacy & Security ▸ Open Anyway, or xattr -dr com.apple.quarantine /Applications/Portain.app. On macOS 15+ the old right-click ▸ Open trick no longer works.

It never starts Docker for you

No daemon, no background agent, no helper install. If Docker is not running the Containers view says so and the Ports view carries on working — lsof does not care.

TCP listeners only

The Ports view is lsof -sTCP:LISTEN. UDP, established connections and unix sockets are not in it.

Someone else's process may refuse

Killing something owned by another user or by the system needs privileges Portain does not ask for. It reports the failure rather than escalating.

The menu bar app builds from source

swift run PortainMenuBar runs it. The released .app bundles the main window only, so the menu bar companion is not in the download yet.

No orchestration

No compose up, no image pulls, no volume or network editing, no builds. Portain shows you what is running and gets out of the way — the rest is still docker in a terminal.

Go see what's running.

Portain is free and open source. Drag it to Applications, allow it once in Privacy & Security, and it never asks you for anything again.

Download for Mac
macOS 14+ · universal binarySource on GitHubbunx portain