Position matters. Everything between node and the script path is a flag for the runtime; everything after it belongs to your program and lands in process.argv. So node --watch server.js --port 8080 watches files and passes --port 8080 on, while node server.js --watch watches nothing.
| Flag | Effect |
|---|---|
| --env-file=.env | Load variables from a dotenv 20,543 file |
| --watch | Restart when a loaded file changes |
| --run <script> | Run a package.json script |
| --test | Run the built-in test runner |
| --inspect | Open the debugger on port 9229 |
| --permission | Enforce the permission model |
| --max-old-space-size=2048 | Cap the V8 86,723 old-space heap, in MB |
The last one is not a Node flag at all. Any option Node does not recognize is passed to V8, which is how you tune the heap — but V8's options carry no stability guarantee, so keep them somewhere easy to edit.
When you cannot control the command line — an inherited Dockerfile, a platform's start button — use NODE_OPTIONS. It is a space-separated list parsed before the command line, so command-line flags win for singleton options and are appended for repeatable ones. Only runtime flags are allowed: a script path or -p there makes Node exit with an error.
export NODE_OPTIONS="--max-old-space-size=4096 --require ./instrument.cjs"
node --max-old-space-size=8192 server.js # 8192 wins; instrument.cjs still loads
NODE_OPTIONS='--require "./my path/file.js"' node server.js