DEV Community

Cover image for Your dev stack is 4 terminal tabs. It could be one labeled stream.
benjamin
benjamin

Posted on

Your dev stack is 4 terminal tabs. It could be one labeled stream.

The daily ritual for anyone running more than one process in local dev: a
terminal tab for the web server, another for the worker, another for the CSS
watcher, maybe a fourth for the queue. Now you're alt-tabbing between tabs trying
to remember which one had the error — and when you're done you kill them one… by…
one (and miss the orphaned tailwind --watch still pinning a CPU core an hour
later).

The fix has existed for over a decade: a Procfile. List your processes once,
run them all together, get their output interleaved into a single stream:

web:    node server.js
worker: node worker.js
css:    npx tailwindcss -i app.css -o public/app.css --watch
Enter fullscreen mode Exit fullscreen mode

The problem isn't the idea — it's the tooling around it.

Why the existing runners didn't fit

  • foreman is the original and it's great — but it's a Ruby gem. If your project is Node or Python, you're installing and maintaining a whole Ruby toolchain solely to babysit your dev processes.
  • overmind is fast and lovely, but it drives everything through tmux — another binary to install, and your output now lives inside a tmux session instead of just scrolling past your terminal.
  • concurrently is genuinely excellent and mature — I'll say that plainly. But it's an npm dependency, and you spell your processes out as command-line args rather than in a checked-in Procfile.

I wanted the foreman experience — a real Procfile, prefixed output, one clean
Ctrl-C — with nothing to install but the tool itself, on whichever runtime
already happens to be on the machine. So I built proctide.

What it does

npx proctide
Enter fullscreen mode Exit fullscreen mode
web    | listening on http://localhost:3000
worker | polling jobs queue…
css    | rebuilt app.css in 42ms
web    | GET /  200  11ms
worker | processed job #1841
^C
proctide: SIGINT received, shutting down…
web    | exited (signal SIGTERM)
worker | exited (signal SIGTERM)
css    | exited (signal SIGTERM)
Enter fullscreen mode Exit fullscreen mode

Every process starts at once. Each line of output — stdout and stderr both
gets tagged with the process name, colored so you can pattern-match at a glance,
and padded so the | separators line up. One Ctrl-C sends SIGTERM to every
process group (so the shells and their grandchildren go down — no orphaned
sleep/watch), waits briefly, then exits. If a process crashes on its own, the
rest are torn down too and proctide exits non-zero — so it behaves in CI and
make targets, not just interactively.

It's the same tool on Node and Python

This is the part I'm a little proud of. proctide ships on both registries:

npx proctide            # Node — zero deps
pipx run proctide       # Python — pure stdlib
Enter fullscreen mode Exit fullscreen mode

Whichever is already on the box, you can run proctide with no extra install. And
it's not two lookalike tools — the parsing-and-formatting core (parseProcfile,
colorFor, prefixLine) is the same set of pure functions in both languages,
driven by one shared input→output vector table. Both test suites run that
identical table, so the two ports are proven to format byte-for-byte the same — I
literally diff their output to prove it.

A few design notes

  • A pure core, an IO shell. All the deterministic work — splitting the Procfile, picking colors, building the padded prefix — lives in pure functions with no spawn, no clock, no signals. That's what makes the cross-language guarantee possible. The messy part (concurrent spawn, interleaved streams, signal forwarding) is isolated in a separate runner module and covered by an integration smoke test, since live interleaved output is inherently nondeterministic and not worth pretending otherwise.
  • Process groups, not just processes. Each child is spawned in its own session/group (detached in Node, start_new_session in Python), so a web: sh -c '… && node server.js' shuts down completely instead of leaking the node. A child that ignores SIGTERM gets SIGKILL as a backstop. The Python port also writes through a shared lock so two processes printing at once never produce a garbled half-and-half line.

And the honest caveat: concurrently is mature and great; if you're already in an
npm-only project it's a fine answer. proctide's pitch is narrow — a real
Procfile, prefixed output, clean process-group shutdown, zero deps on either Node
or Python.
If that combination is what you've been missing, this is for you.

Try it / break it

npx proctide        # or: pipx run proctide
Enter fullscreen mode Exit fullscreen mode

It's MIT and tiny. I'd love to hear which shutdown edge case I haven't thought of
yet — and what are you using today to run your local dev stack: a Procfile runner,
a Makefile with &, a wall of terminal tabs, or something cleverer?

Top comments (2)

Collapse
 
alexshev profile image
Alex Shev

The labeled stream idea is underrated. A lot of local-dev friction is not that processes are hard to start; it is that their output is scattered and unnamed when something goes wrong.

For agent-assisted development this matters even more. If logs are grouped by role and state, an agent can reason about the system without asking the human to paste four terminal tabs into chat.

Collapse
 
_06a3df6b50aec966668fb profile image
benjamin

Appreciate the insights, Alex! The point about reducing friction for AI agents is highly underrated—definitely something I'll keep in mind as AI becomes more integrated into our daily workflows."