Port occupancy map for homelabs

Know which ports are taken, and which are free.

Port-Light merges your host's listen tables, Docker, and Compose files into a single traffic-light grid, so you can tell taken ports from free ones without reaching for ss.

4.7K Docker pulls v0.7.2

45 in use ยท 4 configured

live demo ยท simulated data

How it works

Three sources, one grid.

Nothing to install elsewhere, nothing leaves the machine. Port-Light reads what is already true on your host and merges it into one view.

Host listen tables ยท /proc, ss

TCP/UDP ports actually bound right now.

Docker API

Container names, status, images, published mappings.

Compose files

Ports that are declared โ€” even when the stack is stopped.

In use โ€” something is listening Configured โ€” declared, but quiet Free โ€” offered when you search

Features

For hosts running more than a few stacks.

Search that suggests

Type a port number; if it's taken, nearby free ones light up.

Conflict radar

Two Compose projects claiming the same host port get flagged before they collide.

Multi-host, one screen

Pull occupancy maps from other Port-Light instances over LAN or Tailscale.

Live updates

SSE pushes a refresh the moment occupancy changes; local history records each transition.

Hooks & metrics

Optional webhooks on new listeners and conflicts; Prometheus aggregates when you want them.

Agent-friendly API

GET /api/ports/suggest returns a free port and can reserve it with an expiring lease.

Speaks your theme

Fifteen palettes from Gruvbox to Kanagawa; four UI languages.

Stays on your machine

No telemetry, no accounts, no cloud. Your port map never leaves the host.

For coding agents

A port API your agent can rely on

Coding agents pick ports by guessing until one collides with your stacks. Port-Light exposes a small HTTP API that returns ports which are free right now and can hold them with an expiring lease. An agent skill and an MCP server wrap the same endpoints; AGENT_TOKEN requires an auth header.

$ export PORT_LIGHT_URL=http://127.0.0.1:2100

curl -s "$PORT_LIGHT_URL/api/ports/suggest?count=2&reserve=true&ttl=3600&label=preview"

{
  "ports": [8081, 8082],
  "reserved": [8081, 8082],
  "expires_at": "2026-08-25T13:04:11Z"
}

Reserved ports appear as configured (amber) on every map until the lease expires.

Appearance

Fifteen palettes, straight from the app.

Click one and the whole page re-skins, mirroring the app's theme system. Your pick persists.

Quick start

One compose file.

services:
  port-light:
    image: stepaniah/port-light:v0.7.2
    container_name: port-light
    restart: unless-stopped
    ports:
      - "${PORT_LIGHT_PORT:-2100}:2100"
    volumes:
      - /path/to/your/compose-stacks:/compose:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /proc:/host/proc:ro
      - ./data:/data
    environment:
      COMPOSE_SCAN_DIR: /compose
mkdir -p data
docker compose up -d

โ†’ http://localhost:${PORT_LIGHT_PORT:-2100}

The public port follows PORT_LIGHT_PORT; it defaults to 2100.

Images for linux/amd64 and arm64, also on GHCR. Prefer version tags over latest.

A port occupancy map โ€” not a container manager.

Port-Light doesn't start or stop containers, tail logs, or replace Portainer. It serves one read-only page on your LAN โ€” keep it behind Basic Auth or a reverse proxy and off the public internet. The security checklist collects the hardening notes.