DEV Community

Cover image for PM2 has no web UI. Every open source alternative is dead. So I built one.
Orchid Files
Orchid Files

Posted on

PM2 has no web UI. Every open source alternative is dead. So I built one.

Why I started this project

PM2 is the most popular process manager for Node.js, with 2 million downloads per week, but it's provided as a CLI tool. If you want to view processes in a browser, manage them, and receive notifications via Slack or email, you need to use PM2 Plus. This is a paid, subscription-based cloud service.

All open-source alternatives are built using JS+HTML. They lack modules, type safety, tests, and an architecture designed for extensibility. They are difficult to maintain and develop. You can't simply fork them and add new features without rewriting most of the code. And since they lack tests, you can break anything with your changes.

Almost all open-source solutions have been abandoned. The most popular ones, based on star count, haven't been updated in 2 to 10 years. Some have issues that have been open for years, and pull requests aren't accepted. The authors have abandoned these projects. Yet new repositories pop up every few months, but they're still just plain JS with a single routes file and a few pages on the frontend. Without any well-thought-out architecture.

I've been using pm2 for about 10 years now, and I've wanted to create a pm2 dashboard for a long time. AI agents make this kind of project much faster to build. That doesn't mean this is just some vibe-coding project where I simply asked the AI to create a dashboard and dumped the code into Git. I make 100% of the architectural decisions, review 100% of the code, and I write each new task for it in a new chat. The AI agent doesn't make decisions; it only handles the routine tasks: writing code, tests, and markup.

Fun fact. When I started brainstorming a name for the npm package, I went through dozens of options and settled on pm2-dashboard. It had been taken by an inactive package for 11 years and became available 11 days before I started choosing a project name. I managed to grab it and publish a placeholder. It seems like a huge coincidence that the best possible name for the project became available at just the right moment.


How the project is structured

I decided to build a project that would be very easy to extend and maintain — with full TypeScript coverage, test coverage, linters, and a modular architecture. For the structure, I chose a monorepo approach: apps, packages, scripts, and docs. As usual, packages/shared contains shared modules, constants, types, and helpers.

For the frontend, I chose between Vue, React, and Svelte. I'm familiar with all of them, but I chose Svelte because I prefer its style over React's, and I haven't worked with Vue in a long time. Fewer developers are familiar with it, which might complicate maintenance, but if you work with AI agents and know Tailwind, there won't be any issues.

On the backend, I immediately chose NestJS for its dependency injection and modular architecture; it's better for scalability than Express or Fastify. I also considered AdonisJS, but it's less popular. It might take longer to set up initially, but I simply asked an AI agent to port the existing NestJS code from my other projects. But it pays off in both maintenance and extensibility.

For the database, I chose between PostgreSQL and SQLite. I chose SQLite because so the package can be installed with a single command from npm, and there's no need to spin up a Docker container for the database — the entire database is in a single file. For the ORM, I chose between TypeORM and Drizzle. I chose Drizzle because I hadn't worked with it yet and wanted to gain some experience, and it seems like most new projects are choosing it now.

Between JWT and cookie-based sessions, I chose the latter because JWT offers no advantages for a self-hosted tool with a few users, but it complicates logout and token deletion. Surprisingly, I couldn't find a library that works with express-session and better-sqlite3 and writes sessions to the same database file. connect-sqlite3 writes sessions to a separate file, and better-sqlite3-session-store has a license incompatible with MIT. I built my own session solution in 30 lines of code.

On first launch, a setup wizard appears, prompting you to create a user; after that, access to the interface is available only via username and password. No .env or other configuration files.

It took 5 days from the project idea to a working version and writing this post. Without AI agents, it would have taken much longer. And without AI agents, I probably wouldn't have even started this project.


What's already working

  • List of processes with CPU, RAM, and status
  • Start, stop, and restart for each process, as well as bulk operations
  • Uptime and restart counters
  • Authentication
  • Setup wizard on first launch
  • A single npm package with API and web interface

Next priorities

  • Real-time updates via WebSocket
  • Log viewer
  • Metrics history with graphs
  • Alerts

How to try it

npx pm2-dashboard
Enter fullscreen mode Exit fullscreen mode

It will open at http://localhost:3000. On first run, the CLI prints a setup link.

GitHub: https://github.com/orchidfiles/pm2-dashboard


If you use PM2, I'd love to hear your feedback.
Leave a comment or open an issue on GitHub.

Top comments (3)

Collapse
 
apex_stack profile image
Apex Stack

The SQLite choice is really smart for a self-hosted tool like this — zero-config database in a single file means the barrier to adoption is basically just npx pm2-dashboard. I run a similar stack for a content pipeline that manages processes across 8,000+ pages, and the single-file DB approach saved me from so much operational overhead compared to spinning up a separate Postgres container.

Your point about the session store gap is interesting too. I hit the same kind of "surely someone built this" moment when looking for a lightweight cookie-session + better-sqlite3 combo. Sometimes the 30-line custom solution ends up being more maintainable than wrestling with an abandoned library that almost does what you need.

Curious about your WebSocket plans for real-time updates — are you thinking of pushing full process state on every tick, or diffing against the last known state and sending deltas? At scale (dozens of processes with frequent restarts), the delta approach can cut bandwidth significantly, but it adds complexity around reconnection and state reconciliation. Would love to see how you handle that in the next iteration.

Collapse
 
orchidfiles profile image
Orchid Files

are you thinking of pushing full process state on every tick, or diffing against the last known state and sending deltas?

I usually consider different options before tackling a task. To do this, I use an AI chatbot. But I don't limit myself to a single AI model; I switch between different ones, since they can provide different information in response to the same query. In this case, I don't think there will be much difference between sending all the data or just the delta, in terms of traffic volume. Even if dozens of running processes and WebSockets send information every 2 seconds, it would amount to ~100 MB per day. Given the current global internet infrastructure, this seems like an insignificant amount. But it makes sense to use the delta not so much to save on traffic as to eliminate noise in the updates.

Collapse
 
klement_gunndu profile image
klement Gunndu

Been using PM2 for years and the lack of a proper web dashboard is a real pain point. The fact that you're making 100% of the architecture decisions yourself while using AI for code gen is the right call -- that's how these projects should work.