Skip to content

YTakahashii/portmux

Repository files navigation

PortMux

Tired of Error: listen EADDRINUSE: address already in use :::3000?

PortMux is a developer-focused CLI for running and coordinating background processes. It solves the chronic problem of port conflicts in projects with multiple services, especially when working across several Git branches.

It reserves ports for your process groups before they start and ties each reservation to a Git worktree. When another worktree already owns a port, PortMux fails fast and tells you which one; the select command will stop the old worktree and start the new one so you can reuse the same ports safely.

While tools like pm2 or systemd are excellent for managing production services, PortMux is purpose-built for the development inner loop, prioritizing simplicity and eliminating common frustrations.

Japanese write-up: https://zenn.dev/yuta_takahashi/articles/introduce-portmux-cli

demo

Why PortMux?

PortMux is built on a few core principles to streamline the developer experience:

  • Frictionless Parallelism with Git Worktrees: The core feature. PortMux maps process state to individual Git worktrees. Clone your repository into multiple worktrees (git worktree add ...), and portmux select will stop whichever worktree is holding the ports before starting the one you choose—no manual cleanup required.

  • Predictable by Default: By reserving ports before launching your commands, PortMux fails fast and tells you exactly which port is unavailable. This avoids the pain of one service in a group failing mid-startup because another service took its port.

  • Developer-First Simplicity: PortMux is a lightweight, daemon-less CLI. It manages state in a transparent file-based system within ~/.config/portmux/, giving you full control and visibility without a persistent background process to manage.

Features

  • Git worktree–aware process tracking; if another worktree already holds a port, starts fail fast and portmux select can hand off the running group for you
  • Group-oriented process management with shared start/stop/restart flows
  • Port reservation to avoid collisions before booting services
  • Environment templating with ${VAR} expansion from config or process.env
  • Persistent state for PIDs, ports, and logs under ~/.config/portmux/
  • Git-aware group resolution so you can run commands from any subdirectory

Prerequisites

  • Node.js 20+

Supported OS

OS Status Notes
macOS Supported Actively maintained.
Linux Not yet verified Expected to work on modern distributions; please report compatibility findings.
Windows Experimental Uses wmic for process inspection; unverified and likely incomplete.

Installation

  • Global install: pnpm add -g @portmux/cli or npm install -g @portmux/cli (or run ad hoc via npx @portmux/cli)
  • From source:
    1. pnpm install
    2. pnpm build
    3. pnpm dev:cli -- --help to run the built CLI locally

Quick Start

  1. portmux init in your project root to generate portmux.config.json and register the repo in ~/.config/portmux/config.json (use --force to overwrite). If your repo already includes portmux.config.json (e.g., after cloning), run portmux sync --all for monorepos or any project with multiple groups to register everything without prompts.
  2. Edit the generated config (or review the existing one) and keep your group definitions up to date. Example:
    {
      "$schema": "https://raw.githubusercontent.com/YTakahashii/portmux/main/packages/cli/schemas/portmux.config.schema.json",
      "groups": {
        "app": {
          "description": "Demo group",
          "commands": [
            {
              "name": "web",
              "command": "pnpm dev",
              "ports": [3000],
              "cwd": "./apps/web",
              "env": {
                "API_URL": "http://localhost:3000"
              }
            }
          ]
        }
      }
    }
  3. Ensure the repository is registered in the global config with portmux sync (prefer --all for monorepos or when multiple groups exist).
  4. Start everything with portmux start.
  5. Inspect running processes with portmux ps.
  6. Follow logs with portmux logs <group> <process> and stop with portmux stop [group] [process].

Usage Examples

PortMux's core value shines when you're working on multiple features at once. Here’s how you can use it with Git worktrees to switch between two versions of your app without fighting over ports.

Switching Between Worktrees with select (Recommended)

The portmux select command is the smoothest way to switch contexts. It automatically stops processes running in the current worktree and starts them in the one you select.

  1. Create two worktrees for different features:

    # Create a worktree for feature-a
    git worktree add ../project-feature-a feature-a
    
    # Create another for feature-b
    git worktree add ../project-feature-b feature-b
  2. Start working on feature-a: You can be in any directory of your project. portmux select will find all associated worktrees.

    # Select the first worktree to start its processes
    portmux select
    # ? Select a repository: (Use arrow keys)
    # > project (feature-a) [/path/to/project-feature-a]
    #   project (main) [/path/to/project]
    #   project (feature-b) [/path/to/project-feature-b]
    
    # After selecting, it starts automatically
    # ▶ Starting group 'app' in worktree 'project (feature-a)'...
    # ▶ web (PID: 12345) is running...
  3. Switch to feature-b without changing directories: When you need to work on the other feature, just run select again. You don't even need to cd. PortMux handles stopping the old environment and starting the new one.

    # Still in your original directory
    portmux select
    # ? Select a repository:
    #   project (feature-a) [/path/to/project-feature-a]
    #   project (main) [/path/to/project]
    # > project (feature-b) [/path/to/project-feature-b]
    
    # It gracefully stops the 'feature-a' processes before starting 'feature-b'
    # ▶ Stopping processes for group 'app' in worktree 'project (feature-a)'...
    # ▶ Starting group 'app' in worktree 'project (feature-b)'...
    # ▶ web (PID: 54321) is running...

Basic Commands

The following examples assume you have a group named app with a process named web.

# Start a configured group (auto-resolves when only one exists)
portmux start

# Start a specific group or process
portmux start app
portmux start app web

# Restart or stop
portmux restart app web
portmux stop app
# When multiple groups are running and you want to stop everything at once
portmux stop --all

# Show running state for all worktrees
portmux ps

# Tail logs (default 50 lines, follow)
portmux logs app web
portmux logs app web -n 200 --no-follow

# Choose a registered project and start from the global config
portmux select --all

# Register an existing project config into the global config
portmux sync
portmux sync --all

Command Reference

  • portmux init [--force]: Interactive setup for portmux.config.json and global registration.
  • portmux start [group] [process] [--all]: Start processes with port reservation and env substitution. --all starts every group defined in the project config for the current worktree.
  • portmux restart [group] [process] [--all]: Stop then start using the same resolution rules as start. --all restarts every running process in the current worktree.
  • portmux stop [group] [process] [--all] [-t, --timeout <ms>]: Stop processes; errors when multiple groups are running unless --all, and --timeout controls the wait before SIGKILL (default: 3000 ms).
  • portmux ps: List group, process name, status, and PID.
  • portmux select [--all]: Pick a registered repository and run start; --all includes entries outside Git worktrees.
  • portmux sync [--all] [--group <name>] [--name <alias>] [--dry-run] [--force] [--prune]: Register the current project config in ~/.config/portmux/config.json. When multiple groups exist you must pass --group <name> or --all; otherwise the command exits with an error.
  • portmux logs <group> <process> [-n <lines>] [--no-follow] [-t]: Tail logs with optional timestamps.

Security note

  • PortMux executes command values via your shell (e.g., to allow pipes/redirects). Use configs you trust and review shared portmux.config.json files before running them.

Configuration

Project config: portmux.config.json

  • $schema (optional): Point to node_modules/@portmux/cli/schemas/portmux.config.schema.json for editor IntelliSense.
  • runner.mode (optional): Currently only background is supported.
  • groups (required): Object keyed by group name.
    • description: Group description.
    • commands: Array of processes.
      • name / command (required): Process name and shell command.
      • ports (optional): Port numbers to reserve; accepts integers or ${VAR} placeholders resolved from env then process.env (must resolve to positive integers).
      • cwd (optional): Working directory for the process. Defaults to the project root.
      • env (optional): String map of environment variables; ${VAR} expands from env then process.env (missing values warn and resolve to an empty string).

Global config: ~/.config/portmux/config.json

  • repositories: Map keyed by repository alias.
    • path: Absolute path to the project root.
    • group: Group name in portmux.config.json.
  • logs (optional): Global log settings applied for this user.
    • maxBytes: Per-user log cap in bytes (defaults to 10MB when omitted).
    • disabled: When true, suppresses all process log output.
  • portmux init appends the current project; start/restart/select use this mapping for resolution.
  • portmux sync is the quickest way to register a repo that already ships with portmux.config.json (e.g., after cloning). When only one group exists it registers that group by default; otherwise pass --group <name> or --all (and --prune to drop stale entries that no longer exist on disk).
    • Repository paths are written with the home directory shortened to ~ by default; absolute paths remain supported and are expanded at runtime.

Group Resolution

  • When start/restart omit the group, resolution checks the global config and Git worktree first.
  • If auto-resolution fails, PortMux searches upward from the current directory for portmux.config.json and uses the first group definition.

Logs and State

  • stdout/stderr are written to ~/.config/portmux/logs/; view with portmux logs.
  • Process state, PIDs, and reserved ports persist in ~/.config/portmux/ for reuse by ps and logs.
  • Log files are automatically trimmed to the newest content when they exceed 10MB by default; set a per-user cap via logs.maxBytes in ~/.config/portmux/config.json.
  • Trimming runs at process start and when listing processes (portmux ps), keeping only the tail within the configured limit.
  • Disable logging globally by adding "logs": { "disabled": true } to ~/.config/portmux/config.json (stdout/stderr are ignored when disabled).
  • Log cleanup: portmux stop removes the associated log file, and portmux ps prunes log files not referenced by any recorded process state. No separate prune command is required.

Troubleshooting

portmux start / restart fails with a global-config error

Run portmux sync --all (or --group <name> for a single group) in the repo so it registers in ~/.config/portmux/config.json.

portmux select shows “No selectable groups”

The repository is not registered. Run portmux sync --all in the worktree you want to use.

“The repository for this git worktree is not defined in the global config”

Run portmux sync --all in that worktree to register it.

Port is already reserved / start fails mid-way

Use portmux select to hand off to the active worktree, or portmux stop --all to clear running groups, then retry.

Config not found

Ensure portmux.config.json exists in the project root (re-run portmux init if needed).

Uninstall

To remove PortMux from your system, first uninstall the global package:

  • Using pnpm:
    pnpm remove -g @portmux/cli
  • Using npm:
    npm uninstall -g @portmux/cli

This will remove the portmux command. To completely remove all associated data (including repository history, logs, and process state), delete the configuration directory:

rm -rf ~/.config/portmux

Warning: This action is irreversible and will delete all PortMux settings and log files.

Contributing

See CONTRIBUTING.md for development setup, verification commands, and contribution guidelines.

About

A developer CLI that ends port conflicts. Manages process groups with Git worktree-aware port isolation for parallel development and seamless context switching.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors