Run multiple dev servers locally: which approach fits

Pick the lightest tool that keeps four things under control: where each server’s output goes, what happens when one crashes, which port each one uses, and how you start the same set again tomorrow. Terminal tabs cover two servers. concurrently or a Procfile runner covers a repeatable set in one terminal. Docker Compose covers services that already run as containers. A process manager app such as Rig keeps logs, port handling, restarts, and health checks for your servers in one place. This guide compares them and says when each is the right choice, including when you do not need Rig.

Updated 9 min read

The four problems to solve

Every approach below answers the same four questions, so you can use them to judge any other tool too.

  1. Output. Can you see which server printed a line, and read it after the terminal closes?
  2. Crashes. Do you notice that the worker died, and does anything bring it back?
  3. Ports. What happens when a server asks for a port another process already holds?
  4. Repeatability. Can a teammate, or you next Monday, start the whole set without remembering the order?

The example stack is a web frontend on port 3000, an API on port 4000, a background worker with no port, and a database that already runs in a container.

Option 1: several terminal tabs

Open a tab per server and run each command by hand. There is nothing to install, and for two servers in a short session it is the right choice.

It stops scaling for structural reasons. Each tab holds one process and its own scrollback, so there is no combined view. A crashed server stays down until you look at its tab, and a port clash appears only when a server starts. The set of servers exists only as open tabs, so it is written down nowhere you can reuse.

Option 2: a terminal multiplexer such as tmux

tmux gives one terminal several windows and panes. Detaching leaves the session and the programs in it running, so you can close the terminal and attach again later. A short script builds the same layout every time:

Terminal
tmux new-session -d -s dev -n web 'npm run dev:web'
tmux new-window -t dev: -n api 'npm run dev:api'
tmux new-window -t dev: -n worker 'npm run dev:worker'
tmux attach -t dev

This suits you if you already live in tmux and want each server one window away. Watch for one thing. By default a window closes when its command exits, so a crash can take the error message with it. Set remain-on-exit to keep the window open, and restart the command with respawn-window. A pane-died hook can run that command for you.

Option 3: concurrently or npm-run-all

concurrently runs several commands from one command, and npm-run-all runs several npm scripts. Both live in your package.json, so everyone who clones the repository gets the same start command after a normal npm install.

JSON
{
  "scripts": {
    "dev": "concurrently --names web,api,worker \"npm:dev:web\" \"npm:dev:api\" \"npm:dev:worker\""
  }
}

With npm-run-all the equivalent is run-p --print-label dev:web dev:api dev:worker. Each output line carries the script name. concurrently also has options to restart a failed command (--restart-tries) or to stop the rest when one exits (--kill-others), and run-p stops the others when a script exits with a non-zero code unless you pass --continue-on-error. The npm-run-all README warns that some tools stop colouring their output under --print-label. Both are command runners: which port each server takes is whatever its own script sets.

Option 4: Procfile runners

Foreman, Honcho, Overmind, and heroku local read a Procfile and start its lines:

Text
web: npm run dev:web
api: npm run dev:api
worker: npm run dev:worker

The file is plain text and works outside Node. Runners differ in what happens when one process exits, so kill the worker once and watch. Overmind, for example, stops the others by default, and its -r option restarts the processes you name. Overmind is also the one that changes the workflow, because each process gets its own tmux window and you can attach to one with overmind connect or restart only that one with overmind restart. The Procfile guide covers the syntax, the process types, and each runner’s commands. Foreman sets PORT for each process, starting at the base port you give with -p and rising by 100 for each process line, and your app still has to read it.

Option 5: Docker Compose

Compose is the right tool when the services already exist as containers, such as a database, a cache, or a message broker, or when every developer needs the same environment. One file describes the services, and docker compose up starts them and streams their combined output. docker compose logs shows that output later, with each line labelled by the service it came from.

YAML
services:
  api:
    build: ./api
    ports:
      - "4000:4000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 10s
      timeout: 3s
      retries: 5
  worker:
    build: ./api
    command: node src/worker.js
    depends_on:
      api:
        condition: service_healthy

Here unless-stopped restarts the container whatever its exit code, but not after you stop it, and the health check gates the worker: Compose waits until api is healthy before it starts worker. The left side of "4000:4000" is the host port, so it can still clash with a process that is not in the file.

The cost is that your code runs inside a container. Each service you edit needs an image or Dockerfile, and edits reach the container through a bind mount or through Compose Watch. Docker’s documentation says Watch sync rules can replace bind mounts for many development uses. A practical split is databases in Compose and the code you are editing outside it.

Option 6: a process manager app such as Rig

A process manager app watches the servers instead of only starting them. This is what Rig does, from its own behaviour:

  • Detection. Rig detects package.json scripts, a Procfile, and a Compose file in a project, and offers each as a script you can start. For a Compose file it runs docker compose up. Starting a group uses health-aware ordering.
  • Logs. One log per project. Rig keeps the last 300,000 characters in memory and writes the log to disk, rotating at 2 MB into a single .log.1 file.
  • Ports. who_owns_port shows the process holding a port and suggests the first free port in the 50 above the one you asked for. kill_port sends SIGTERM and then SIGKILL if the port is still bound. When a port is taken, start_project can kill the owner, switch to a suggested port, or report the conflict. Vite starts with --port N --strictPort.
  • Auto-restart. After a crash or failed health checks, Rig can restart a process with exponential backoff, capped at 30 seconds, and by default it pauses after 5 attempts. It frees an EADDRINUSE port before it restarts.
  • Health polling. Every 5 seconds Rig sends a GET request with a 4 second timeout to the first of the project’s URLs that answers. A status from 200 to 499 counts as healthy, and a 5xx or an error counts as unhealthy.

Rig is not the right choice in every case. Its health check is an HTTP request. When no URL answers, as with a worker that serves no HTTP, the status is unknown, and a 404 counts as healthy, so the check shows that something answered, not that the page is right. Each Procfile process type becomes a script you select, with extra scripts running alongside it, which is a different model from a runner that starts every line in one command. If your whole stack is containers, Compose already handles it, and Rig runs it as docker compose up rather than replacing it. Check the download page for supported platforms before you plan around it.

Decision table

ApproachSetup effortLogsCrashesPortsMain limit
Terminal tabsNone. One tab per server.One scrollback per tab, no combined view.Nothing restarts a server. You notice by looking.You choose each port. A clash shows when the server starts.The set of servers lives in open tabs.
tmuxInstall tmux and keep a script for the layout.One window per server, and the session survives a detach.A window closes on exit unless remain-on-exit is set. respawn-window restarts it.You choose each port.Terminal only. You maintain the layout script.
concurrently or npm-run-allOne dev dependency and one script.One stream, with a label on each line.concurrently can restart a failed command or stop the others. run-p stops the others on a non-zero exit.Set in each script.Runs commands. Ports and readiness are left to your scripts.
Procfile runnersA Procfile and a runner install.Depends on the runner. Overmind gives each process its own tmux window.Differs by runner. Overmind stops the others by default.Foreman sets PORT, and the app must read it.Needs a runner install, and Overmind needs tmux.
Docker ComposeA compose file plus an image or Dockerfile per service.docker compose logs labels each line by service.A restart policy per service, with health checks gating depends_on.ports maps a host port to a container port. The host side can still clash.Your code runs in a container, so live editing needs a bind mount or Compose Watch.
RigInstall the app. It detects scripts, a Procfile, and a Compose file.One log per project, kept in memory and on disk.Restart with backoff after a crash or failed health check, paused after 5 attempts by default.who_owns_port, kill_port, and a suggested free port.Health checks are HTTP only, and a Procfile process type is one script you select.

How to choose

  • Two servers, a short session: use tabs.
  • A team that wants npm run dev to work after a normal npm install: use concurrently or npm-run-all.
  • An app that deploys with a Procfile: use a Procfile runner, so the file is the single list.
  • A terminal-first workflow where you want detach and attach: use tmux, or Overmind if you already have a Procfile.
  • Databases, caches, and brokers, or identical environments for every developer: use Compose for those services.
  • Several servers that you restart daily, ports that collide, or crashes you want caught: use a process manager app.

These combine. A practical setup is Compose for the database, a Procfile for the app processes, and a process manager above both.

The natural next step

If the tab problem is the one you recognise, the comparison with terminal tabs sets out what changes, the features page lists the process controls, and you can download Rig to try it on a project you already run. If the port problem is the sharper one, find and free a port works without any of these tools.

Frequently asked questions

What is the simplest way to run a frontend and an API at the same time?

Two terminal tabs, or one concurrently script if you want a single command. Add structure only when you start missing crashes, hitting port clashes, or forgetting the start order.

Should I run my dev servers in Docker Compose?

Compose fits services you already run as containers, such as databases, caches, and brokers. For code you edit all day, running it outside Compose avoids the bind-mount or Compose Watch setup, and it can still reach the containers through their published ports.

How do I stop two dev servers fighting over a port?

Give each server its own fixed port in its config, and check who owns a port before you start. The guide to finding and freeing a port has the commands for each operating system. Rig’s who_owns_port looks the owner up with lsof -nP -iTCP:<port> -sTCP:LISTEN (netstat and tasklist on Windows), and kill_port frees the port.

Can one command start everything and show which server printed each line?

Yes. concurrently --names and run-p --print-label label each line with the script name, and docker compose logs labels each line with the service. Overmind lets you attach to one process by name. Check how your Procfile runner formats its output.

Does Rig replace Docker Compose?

No. When a project has a compose file, Rig runs docker compose up as one of its scripts. Compose remains the place to define container services.

When is a process manager worth installing?

When the manual approach has a visible cost: you restart the same servers daily, ports collide, or a crash goes unnoticed. Until then, a script or a Procfile is enough.

Sources

  1. tmux wiki: Getting startedOfficial documentation, checked 13 October 2026
  2. tmux(1) manual page, OpenBSDOfficial documentation, checked 13 October 2026
  3. concurrently READMEOfficial documentation, checked 13 October 2026
  4. npm-run-all: run-pOfficial documentation, checked 13 October 2026
  5. npm-run-all: npm-run-all commandOfficial documentation, checked 13 October 2026
  6. Heroku Dev Center: Running Apps LocallyOfficial documentation, checked 13 October 2026
  7. foreman(1): manage Procfile-based applicationsOfficial documentation, checked 13 October 2026
  8. Honcho: manage Procfile-based applicationsOfficial documentation, checked 13 October 2026
  9. Overmind: process manager for Procfile-based applications and tmuxOfficial documentation, checked 13 October 2026
  10. Docker: define services in Docker ComposeOfficial documentation, checked 13 October 2026
  11. Docker: control startup and shutdown order in ComposeOfficial documentation, checked 13 October 2026
  12. Docker: docker compose logsOfficial documentation, checked 13 October 2026
  13. Docker: docker compose upOfficial documentation, checked 13 October 2026
  14. Docker: use Compose WatchOfficial documentation, checked 13 October 2026
  15. Rig documentation: Procfile projects (Rig behaviour confirmed in Rig’s source code)Rig source code, checked 13 October 2026
  16. Rig documentation: Health-aware start orderRig source code, checked 13 October 2026

Run your whole stack from one window

Rig is a Mac app that starts, stops, and watches the processes in your project, with logs and ports in one place. Local use is free, and your source code stays on your Mac.

Related

https://userig.app/learn/run-multiple-dev-servers-locally