Zero-config dev server
Configure lpm.json once, then run lpm dev — HTTPS, tunnel, and multi-service orchestration.
lpm dev is the marquee command of the Dev section. With nothing configured, it runs your dev script. With a few lines of lpm.json, it picks up the right Node version, syncs deps, loads .env, serves over HTTPS, exposes a public tunnel, and orchestrates multiple services with readiness checks. This guide covers the path from "works in my repo" to "works the same for the whole team."
Step 0: Just run it
lpm devIf your package.json has a dev script, lpm dev runs it. Stop here if that's all you need.
The rest of this guide is about turning that into something repeatable: a checked-in config that gives every contributor the same setup with no per-machine fiddling.
Step 1: Pin the Node version
{
"runtime": { "node": ">=22.0.0" }
}lpm dev will use the pinned version, auto-installing it via lpm use if missing. Node's fall-back chain is lpm.json > runtime.node → package.json > engines.node → .nvmrc → .node-version; Bun is read from lpm.json > runtime.bun. See Managed runtimes for the full detection contract.
lpm use node@22 # install + pin in one step (writes lpm.json)Step 2: Map env files to scripts
{
"env": {
"dev": ".env.development",
"staging": ".env.staging",
"prod": ".env.production"
}
}lpm run dev now loads .env.development automatically. lpm run staging loads .env.staging. CLI override: lpm dev --env=preview loads .env.preview regardless.
For env-var validation (CI catches missing required vars before they explode at runtime):
{
"envSchema": {
"vars": {
"DATABASE_URL": { "required": true, "format": "url" },
"API_KEY": { "required": true, "secret": true }
}
}
}The check runs before every lpm run / lpm dev / lpm exec. Skip with --no-env-check.
Step 3: Turn on HTTPS
{ "https": true }Plus a one-time CA install:
lpm cert trustNow lpm dev serves over https://localhost. Browsers trust the cert. See Local HTTPS for the file layout.
Step 4: Add a tunnel
{ "tunnel": { "domain": "acme-api.lpm.llc" } }Plus the claim (Pro/Org only):
lpm tunnel claim acme-api.lpm.llcNow lpm dev exposes the dev server at https://acme-api.lpm.llc. Webhooks to the public URL are captured to disk for replay:
lpm tunnel inspect # browse captured events
lpm tunnel replay 3 # re-deliver event #3 to localhost
lpm tunnel inspect --ui # browser-based inspectorFree users get an ephemeral random domain on every run. To keep one stable URL, claim it.
Step 5: Multi-service orchestration
{
"services": {
"db": {
"command": "docker compose up postgres",
"readyPort": 5432,
"readyTimeout": 60
},
"api": {
"command": "node server.js",
"port": 4000,
"dependsOn": ["db"],
"env": { "DATABASE_URL": "postgres://localhost:5432/myapp" }
},
"web": {
"command": "next dev",
"port": 3000,
"primary": true
}
}
}lpm dev now starts each service, waits for readiness (TCP port poll, or HTTP via readyUrl), and prefixes each service's logs:
[db] ✔ ready (0.8s)
[api] ✔ ready (3.4s)
[web] ✔ ready (1.2s)
⌘ Opening https://localhost:3000The primary service is the one that receives --https / --tunnel / --network and the browser-open. Mark exactly one service as primary: true.
For better-than-prefixed-logs viewing:
lpm dev --dashboardTUI dashboard with per-service log panels and webhook inspection.
Step 6: Task graph and caching
For things you build, not run-and-watch — define them in tasks for caching and dependency ordering:
{
"tasks": {
"build": {
"command": "tsup",
"dependsOn": ["^build"],
"cache": true,
"outputs": ["dist/**"],
"inputs": ["src/**", "package.json"]
}
}
}^build means "build me only after every upstream workspace dep has built." cache: true caches the result keyed by inputs; a re-run with unchanged inputs replays instantly. See lpm run and Task runner.
Common pitfalls
lpm cert trustonly needs to run once per machine. Teammates each run it on their laptop; CI doesn't need it.- Tunnel domain
claimis per-organization or per-user. Claim it once, commitlpm.json, every teammate uses the same URL. - Mark exactly one service
primary: true. Otherwiselpm dev's flag-routing and browser-open don't have a clear target. readyPortdefaults toportif absent. If your service listens on a different port for readiness checks (uncommon), setreadyPortexplicitly.
See also
lpm dev— full command referencelpm.jsonreference — every field, every typelpm cert— local HTTPS detaillpm tunnel— claim, inspect, replay- Task runner — caching internals