百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
S

sonar

> DevOps
开源

用于检查和管理在本地端口上监听的服务的 CLI 工具

1.1K stars0 点赞0 次浏览
访问官网GitHub

工具介绍

用于检查和管理在本地端口上监听的服务的 CLI 工具

Sonar shows everything listening on localhost and puts it in order: every port belongs to a group — normally the repository it was started from — and inside that group to a named service. Start your dev servers with sonar start and the whole project becomes one thing you can list as a tree, wait for, tail, and stop with a single command. Docker containers, Compose projects and processes you started by hand are picked up too, without any configuration.

$ sonar list --tree
my-app  (3 ports, running)                        ~/code/my-app
├─ 5432  db          postgres:17                  http://localhost:5432
├─ 5173  frontend    vite (v5.4)                  http://localhost:5173
└─ 8000  api         uvicorn app:app              http://localhost:8000
ungrouped (1 port)
└─ 3000  next-server (v16.1.6)                    http://localhost:3000

Install

Homebrew (macOS / Linux)

brew install raskrebs/sonar/sonar

Homebrew 6 refuses formulae from third-party taps until you trust the tap once (Error: Refusing to load formula raskrebs/sonar/sonar from untrusted tap):

brew trust raskrebs/sonar

Install script

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | bash

Downloads the latest binary to ~/.local/bin and adds it to your PATH if needed. Restart your terminal or source ~/.zshrc.

On Windows (PowerShell):

irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex

Custom install location:

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_INSTALL_DIR=/usr/local/bin bash

Install a specific version:

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_VERSION=vX.Y.Z bash
$env:SONAR_VERSION="vX.Y.Z"; irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex

Using Go

go install github.com/raskrebs/sonar@latest

Shell completions (tab-complete port numbers):

sonar completion zsh > "${fpath[1]}/_sonar"   # zsh
sonar completion bash > /etc/bash_completion.d/sonar  # bash
sonar completion fish | source                 # fish

Sixty seconds

Describe how your project runs in a sonar.yaml at the repository root, in place of a dev.sh:

name: my-app
services:
  - name: db
    cmd: docker compose up db
    port: 5432
  - name: api
    cmd: uv run uvicorn app:app --port ${port}
    port: auto
    depends_on: [db]
  - name: frontend
    cmd: npm run dev -- --port ${port} --strictPort
    port: auto
    depends_on: [api]
    env:
      VITE_API_URL: ${api.url}

Then start it:

sonar start
  ✓ db        http://localhost:5432   pid 41022  ~/.config/sonar/logs/my-app/db.log
  ✓ api       http://localhost:21408  pid 41040  ~/.config/sonar/logs/my-app/api.log
  ✓ frontend  http://localhost:21409  pid 41077  ~/.config/sonar/logs/my-app/frontend.log

3 started
following the logs; Ctrl+C stops the 3 services started here
api      | INFO:     Uvicorn running on http://127.0.0.1:21408
frontend | VITE v5.4  ready in 312 ms

Sonar picks a free port for every port: auto service and tells each service where the others are, so a second worktree runs next to the first without a single port clashing. sonar start -d does the same in the background, and sonar down stops it and gives the ports back. In another terminal:

sonar list --tree
my-app  (3 ports, running)                        ~/code/my-app
├─ 5432   db          postgres:17                 http://localhost:5432
├─ 21408  api         uvicorn app:app             http://localhost:21408
└─ 21409  frontend    vite (v5.4)                 http://localhost:21409

No sonar.yaml? sonar start -- npm run dev runs one command as a named service, and sonar list and sonar kill work on anything that is listening.

Examples below marked # check are executed against a fresh build by scripts/readme-check.sh on every CI run.

Commands

sonar list

sonar list
sonar list --tree
sonar list --group my-app
sonar list --json
# check
sonar list --stats             # CPU, memory, threads, uptime, state
sonar list --health            # HTTP health checks
sonar list --filter docker     # only Docker ports
sonar list --sort name         # port | pid | name | type
sonar list -a                  # include desktop apps
sonar list -c port,process,group,cpu,mem
sonar list --host user@server  # scan a remote machine over SSH

Default columns are port, process, group, container, image, containerport, url, where process shows the name you gave the port (sonar rename), then the service name, then what was detected.

Available columns: port, process, pid, type, url, group, cpu, mem, threads, uptime, state, connections, health, latency, container, image, containerport, compose, project, user, bind, ip.

Desktop apps and system services that happen to listen — Figma, Discord, Spotify, ControlCenter, macOS .app bundles, /System/Library/ daemons — are hidden unless you pass -a.

sonar start

Start a project from its sonar.yaml:

sonar start                    # every service in the nearest sonar.yaml
sonar start api frontend       # only these (they still wait for their dependencies)
sonar start ../other-project   # the sonar.yaml in or above another directory
sonar start -d                 # in the background, like `sonar up`

In the foreground, sonar starts the services through the daemon, follows their logs with the service name in front of every line, and when you press Ctrl+C stops the services it started. A service that was already running is left alone, and sonar returns by itself once every service it started has exited.

Arguments before -- name a directory or services; everything after -- is a command. Or run one command as a named service in a group:

sonar start -- npm run dev
sonar start --group my-app --name frontend -- npm run dev
sonar start --port 5173 -- npm run dev        # expected port, before it binds
sonar start --detach --name api -- uv run uvicorn app:app
sonar start --list

Nothing has to be passed:

  • Group — --group, else the name in the nearest sonar.yaml, else the git root's directory name (a worktree becomes repo@worktree), else the name of the current directory.
  • Name — --name, else the sonar.yaml service whose cmd matches, else inferred from the command (npm run dev → dev, uv run api → api, python -m uvicorn → uvicorn, ./dev.sh → dev.sh).
  • Port — --port is a hint, not a binding: the run shows as starting until the port is actually listening, and the daemon uses it to match the process to the port.

The child inherits stdin, stdout, stderr, cwd and environment, plus SONAR_GROUP, SONAR_NAME and SONAR_RUN_ID. It gets its own process group, so sonar kill takes down the whole tree — a dev server with its watchers and workers. Ctrl+C is forwarded, and sonar exits with the child's exit code.

--detach returns immediately and writes the output to ~/.config/sonar/logs//.log. --list shows what sonar started, and under it what has since ended — with the exit code, and whether it crashed or was stopped (--json for the machine-readable form):

sonar start --list
sonar start --detach --name demo --port 8123 -- sleep 5
sonar start --list --json
# check

sonar.yaml

A project names itself and its services in a sonar.yaml at the repository root. It is optional — sonar groups by git root without it — and it is meant to be committed:

name: my-app
services:
  - name: db
    cmd: docker compose up db
    port: 5432
    health: /
    description: Postgres 17
    icon: database
    color: "#4f8cc9"
  - name: api
    cmd: uv run uvicorn app:app --port 8000
    cwd: backend
    port: 8000
    health: /healthz
    depends_on: [db]
  - name: frontend
    cmd: npm run dev
    port: 5173
    depends_on: [api]
ports: [9229]        # ports that belong to this project without a service
  • name — the group name. No slashes, no whitespace.
  • cmd, cwd, port — how sonar start starts the service. cwd is relative to the file and may not escape its directory.
  • health — an HTTP path the daemon polls while the service is up, so a service can be running but not yet healthy. It reports ok, fail or unknown, with the reason for a failure.
  • description, icon, color — free-form metadata for the desktop app; sonar never infers them.
  • depends_on — start order. Naming a service that is not in the file, or a cycle, is an error; an invalid file is reported once and never stops a scan.
  • env — variables set for the service when sonar starts it.

port: auto and references

A service can leave its port to sonar, and find the others through its environment instead of a hard-coded localhost:8080:

services:
  - name: api
    cmd: uv run uvicorn app:app --port ${port}
    port: auto
  - name: frontend
    cmd: npm run dev -- --port ${port} --strictPort
    port: auto
    depends_on: [api]
    env:
      VITE_API_URL: ${api.url}

sonar start claims a free port for every port: auto service it starts: the same one each time in the same checkout, and a different one in every other checkout, so a worktree never collides with the main one. The service gets it as PORT, SONAR_PORT and ${port}, and it has to use it — read PORT, or pass ${port} on its command line. A service that is already running keeps its port, so one started later still finds it.

${port} and ${url} (http://localhost:) are the service's own; ${.port} and ${.url} are another's. They work in cmd and in env. A reference to a service that is not in the file, or to one with no port, is an error when the file is loaded. Anything else between ${ and }, like ${HOME}, is left exactly as written.

sonar.yml is read if that is how you spell it; sonar init always writes sonar.yaml. The file used to be the dotfile .sonar.yaml, and that name still works: sonar looks for sonar.yaml, sonar.yml, .sonar.yaml and .sonar.yml, in that order, and reads the first one it finds. Edits go back to the file sonar read, so nothing is renamed behind your back. sonar doctor points out a file under the old name, and sonar doctor --fix renames it (with git mv when the file is tracked). The daemon watches the projects it knows about and picks up edits to the file without a restart. Every edit sonar makes — from the desktop app, from sonar groups add, rename and remove, from an agent — goes through the daemon, which re-renders the file from its own syntax tree, so comments, key order and layout survive an edit that adds, renames or removes a service just as they survive a metadata change. The one exception: extra spaces lining a trailing comment up (cmd: x # note) collapse to one, because the YAML library keeps the comment but not its column.

sonar up

sonar up                       # the sonar.yaml at or above this directory
sonar up my-app                # a group by name
sonar up --only api,frontend
sonar up --json

sonar up is sonar start -d, which can also name a group from anywhere and start it on another host. It starts every service the group's sonar.yaml declares, in depends_on order: a service waits for the ports its dependencies declare before it is started, and one that is already listening is skipped. Each runs detached in its own process group, with its output in ~/.config/sonar/logs//.log, and with the environment of the shell you ran sonar up in, plus PORT for a service with a port.

  ✓ db        http://localhost:5432  pid 41022  ~/.config/sonar/logs/my-app/db.log
  - api       already runni

Issues· 0 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

Goclideveloper-toolsdockergolang

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类DevOps
定价开源

> 相关工具

D
Docker
容器化平台,标准化应用交付
G
GitHub Actions
GitHub 原生 CI/CD 工作流
N
Nginx
高性能 Web 服务器与反向代理