cmd / procman

procman is a process manager for local development on macOS.

It is open source.

Install:

go install github.com/croaky/procman@latest

Define processes in Procfile.dev. For example:

clock: bundle exec ruby cmd/clock.rb
esbuild: ./bin/build-js --watch
queues: bundle exec ruby cmd/queues.rb
web: bundle exec ruby cmd/web.rb

Run processes by name, comma-delimited:

procman esbuild,web

There are no options or flags.

procman combines the output:

esbuild | bundling js/app.ts, css/app.css
esbuild | wrote public/js/app-JBTSFCY2.js
esbuild | wrote public/css/app-2H67SL6V.css
esbuild | watching...
web     | [67296] Puma starting in cluster mode...
web     | [67296] * Puma version: 6.4.2 (ruby 3.3.0-p0) ("The Eagle of Durango")
web     | [67296] *  Min threads: 12
web     | [67296] *  Max threads: 12
web     | [67296] *  Environment: development
web     | [67296] *   Master PID: 67296
web     | [67296] *      Workers: 2
web     | [67296] *     Restarts: (✔) hot (✖) phased
web     | [67296] * Preloading application
web     | [67296] * Listening on http://0.0.0.0:3000
web     | [67296] Use Ctrl-C to stop
web     | [67296] - Worker 1 (PID: 67331) booted in 0.9s, phase: 0
web     | [67296] - Worker 0 (PID: 67330) booted in 0.9s, phase: 0

procman runs until it receives SIGINT (Ctrl+C), SIGTERM, or SIGHUP.

When one process exits, procman sends SIGINT to the others, waits 5 seconds, and then sends SIGKILL to any that remain.

procman runs one process per definition. It does not load .env.

File watching

Add # watch: PATTERNS to restart a process when files change:

clock: bundle exec ruby cmd/clock.rb    # watch: lib/**/*.rb
esbuild: ./bin/build-js --watch
queues: bundle exec ruby cmd/queues.rb  # watch: lib/**/*.rb
web: bundle exec ruby cmd/web.rb        # watch: lib/**/*.rb,ui/views/**/*.haml

Patterns are relative to the directory with Procfile.dev. Globs support * (single directory) and ** (recursive). Separate patterns with commas. procman ignores directories matched by .gitignore (and .git), so node_modules and build output do not consume watch descriptors.

When files change, procman sends SIGINT, waits for the process to exit, and restarts it. It debounces changes (100ms) so a multi-file save restarts once. It serializes restarts per process.

Reloading at the process manager level works in any language without framework support.

Kill a process on a port

When a process outlives procman and holds its port, a script on $PATH clears it:

kill-pid-on-port 3000
#!/bin/bash

set -euo pipefail

lsof -n -i :"$1" | grep LISTEN | awk '{ print $2 }' | xargs kill

lsof -ti :3000 | xargs kill is shorter, but -ti returns every process on the port, clients included. grep LISTEN limits it to the server.

← All articles