PM is a process manager for Linux: declare your long-running processes in a config file, and PM starts them, keeps them running in the background, restarts them when they crash or when the code changes, and shows you status, logs, and a TUI dashboard. It's like pm2 — but a single static binary with no Node.js and no JS scripts, and declarative configs in six formats.
- Single binary. One static Linux binary — no Node.js, no JS scripts. Also installable with
go install github.com/rprtr258/pm@latest. - Six config formats. Jsonnet (primary, back-compatible with JSON), YAML, TOML, INI, HCL, JSON — pick one per project.
- Configs are code. Jsonnet configs can use loops to create lists of similar processes, imports,
dotenv, and external variables. - Self-healing. Auto-restart with a retry budget (
--max-restarts), restart on file changes (watch), periodic restarts on a cron schedule (cron). - Groups and ordering. Every command accepts ids, names, or tags —
pm stop all.depends_onstarts processes in dependency order. - Survives reboots. Processes with
startup: truerun on boot via an installed systemd service. - Linux only. PM is available only for Linux due to heavy usage of Linux mechanisms.
# pm.yaml
web:
command: "python3"
args: ["-m", "http.server", "8345"]
tags: ["demo"]$ pm run -f pm.yaml
web
$ pm list
╭────┬──────┬─────────┬────────┬──────────┬───────┬──────────╮
│ id │ name │ status │ uptime │ tags │ cpu │ memory │
├────┼──────┼─────────┼────────┼──────────┼───────┼──────────┤
│ 9 │ web │ running │ 7s │ demo all │ 0.54% │ 22.14 MB │
╰────┴──────┴─────────┴────────┴──────────┴───────┴──────────╯
$ pm logs demo
web | Serving HTTP on 0.0.0.0 port 8345 (http://0.0.0.0:8345/) ...Download the latest binary from the releases page:
# download binary
wget https://github.com/rprtr258/pm/releases/latest/download/pm_linux_amd64
# make binary executable
chmod +x pm_linux_amd64
# move it to a directory in your $PATH, renaming it to pm
sudo mv pm_linux_amd64 /usr/local/bin/pmOr install from source with Go:
go install github.com/rprtr258/pm@latest-
Write a config file — say
pm.yaml— listing your processes:web: command: "node" args: ["server.js"] tags: ["web"] startup: true worker: command: "python3" args: ["-u", "worker.py"] watch: "\\.py$"
-
Start everything in the config:
pm run -f pm.yaml
Processes keep running in the background; you can close the terminal.
-
Watch what's going on:
pm list # status, uptime, cpu, memory pm logs all # follow logs of every process
-
Control processes — by id, name, or tag:
pm stop web pm restart worker pm delete all # stop and remove everything -
To run processes on system startup, install the systemd service once (the unit hardcodes
ExecStart=/usr/bin/pm, and--usernamemakes the service run processes as your user instead of root):# make the binary reachable as /usr/bin/pm sudo ln -s "$(command -v pm)" /usr/bin/pm # install systemd service sudo pm startup systemd --username <your-username> # run all processes with startup right now pm startup
PM supports multiple configuration formats for defining processes. The original jsonnet format is supported, along with several additional formats for flexibility:
- JSONNet (
.jsonnet) — the primary configuration format, fully compatible with plain JSON. - YAML (
.yaml,.yml) — human-readable data serialization standard. - TOML (
.toml) — Tom's Obvious, Minimal Language configuration format. - INI (
.ini,.cfg,.conf) — classic section-based configuration format. - HCL (
.hcl) — HashiCorp configuration language. - JSON (
.json) — plain data-interchange format.
All formats define a list of processes with the same schema:
| Field | Type | Description |
|---|---|---|
command |
string |
Command to execute (required) |
name |
string |
Process name; the section key in YAML/TOML/INI/HCL, a field in JSON/JSONNet. Auto-generated if omitted |
args |
array(string) |
Command arguments |
cwd |
string |
Working directory |
env |
map(string, string) |
Environment variables (name: value pairs) |
tags |
array(string) |
Process tags for filtering; every process gets the all tag automatically |
watch |
string |
File pattern to watch for restarts (regex) |
startup |
boolean |
Start process on system startup |
depends_on |
array(string) |
Process names that must start first |
cron |
string |
Cron expression for periodic restarts |
Auto-restart budget is set per invocation with pm run --max-restarts COUNT; see pm run --help for all flags.
Example in jsonnet:
[
{
name: "web-server",
command: "node",
args: ["server.js"],
env: {
PORT: "3000",
NODE_ENV: "production"
},
tags: ["web"],
startup: true
}
]The same process in YAML:
web-server:
command: "node"
args: ["server.js"]
env:
PORT: "3000"
NODE_ENV: "production"
tags: ["web"]
startup: trueThe same process in TOML, INI, HCL, and JSON: see docs/examples.
Most fresh usage descriptions can be seen using pm <command> --help.
# run process using command
pm run go run main.go
# run processes from config file
pm run --config config.jsonnetpm listListing formats: table, compact, json, short, or any Go template rendered with the process data.
pm start [ID/NAME/TAG]...pm stop [ID/NAME/TAG]...
# e.g. stop all added processes (all processes has tag `all` by default)
pm stop allWhen deleting process, they are first stopped, then removed from pm.
pm delete [ID/NAME/TAG]...
# e.g. delete all processes
pm delete allpm logs web # watch process logs
pm attach web # attach to process stdin/stdout
pm inspect web # inspect a process
pm restart web # restart
pm signal SIGSTOP web # send an arbitrary signal
pm tui # open TUI dashboardflowchart TB
0( )
S(Stopped)
C(Created)
R(Running)
A{{autorestart/watch enabled?}}
0 -->|new process| S
subgraph Running
direction TB
C -->|process started| R
R -->|process died| A
end
A -->|yes| C
A -->|no| S
Running -->|stop| S
S -->|start| C
pm consists of two parts:
- cli client - requests server, launches/stops shim processes
- shim - monitors and restarts processes, handle watches, signals and shutdowns
pm uses XDG specification, so db and logs are in ~/.local/share/pm and config is ~/.config/pm.json. XDG_DATA_HOME and XDG_CONFIG_HOME environment variables can be used to change this. Layout is following:
~/.config/pm.json # pm config file
~/.local/share/pm/
├──db/ # database tables
│ └──<ID> # process info
└──logs/ # processes logs
├──<ID>.stdout # stdout of process with id ID
└──<ID>.stderr # stderr of process with id IDOn master branch:
git tag v1.2.3
git push --tags
GITHUB_TOKEN=<token> goreleaser release --clean