Port management
Understand how LPM CLI assigns ports, handles conflicts, and connects development services.
Port management keeps a multi-service development run usable during a preferred-port conflict.
LPM CLI assigns final service ports, saves local replacements, injects peer addresses, and selects the browser-facing endpoint.
Declare the project topology in lpm.json. Use lpm ports to inspect or reset machine-local port state.
Understand the port types
One development run can use four types of ports:
| Port type | Purpose |
|---|---|
| Preferred port | The services.<name>.port value in lpm.json |
| Assigned port | The port that the child service uses after conflict handling |
| Public port | The browser-facing HTTPS, proxy, or network listener |
| Readiness target | The readyPort or readyUrl used during startup |
These ports can have the same number in a simple HTTP project. HTTPS and proxy workflows often use separate public and child ports.
Configure services in lpm.json
Define each development service below services:
{
"$schema": "https://cli.lpm.dev/schemas/lpm.json",
"services": {
"db": {
"command": "docker compose up postgres",
"port": 5432,
"readyTimeout": 60
},
"api": {
"command": "node api.js",
"port": 4000,
"readyUrl": "http://localhost:4000/health",
"dependsOn": ["db"]
},
"web": {
"command": "vite",
"port": 5173,
"dependsOn": ["api"],
"primary": true
}
}
}Start the complete service graph:
lpm devThe example starts db, then api, then web. LPM CLI starts independent services in the same dependency level together.
Service fields related to ports
| Field | Purpose |
|---|---|
port | Preferred listener port for the service |
readyPort | Separate TCP readiness target |
readyUrl | HTTP readiness target that must return a 2xx response |
readyTimeout | Maximum readiness wait, in seconds. The default is 30. |
dependsOn | Services that enter their ready state before this service starts |
primary | Service that receives browser, HTTPS, network, and tunnel handling |
host | Friendly hostname that routes to the final assigned port |
restart | Restart the service after a non-zero exit |
cwd | Working directory relative to the project root |
See the lpm.json service reference for every field.
Assign service ports
LPM CLI assigns ports before it starts the service commands. A service gets a managed port in these cases:
- The service has a
portvalue. - The service is the primary service.
- The service has a
hostvalue.
If port is absent, the primary and host-routed services still receive a port. Automatic assignment starts at port 3000 and selects the next available port.
LPM CLI passes the final port through PORT. It also adds framework-specific port arguments where the framework requires them.
For example, Vite receives a strict assigned port. Next.js, Nuxt, SvelteKit, Remix, and Astro receive their supported port argument.
A service with only readyPort does not receive a managed service port. Its command must start the process that owns the readiness target.
Replace a busy port
If a preferred port is busy, LPM CLI selects the next available port. It reports available process-owner information.
For example, a preferred port of 5173 can become 5174:
web → :5174 (port 5173 in use by another process)LPM CLI does not edit lpm.json and does not stop the existing process. It saves the replacement for this project in:
~/.lpm/ports.tomlThe next lpm dev run tries the saved replacement before the preferred port. The service can continue to use 5174 after 5173 becomes free.
Concurrent lpm dev runs reserve assigned ports. This reservation prevents two LPM CLI processes from selecting the same replacement.
Return to the preferred port
Inspect the preferred port before you stop its owner:
lpm ports inspect 5173If the owner can stop safely, stop it and clear the saved replacement:
lpm ports kill 5173
lpm ports reset
lpm devlpm ports reset clears only the saved replacements for the current project. It does not stop processes or edit lpm.json.
If the preferred port remains busy, the next development run selects another replacement.
Choose the primary service
The primary service provides the main development endpoint. It receives these features:
- Browser-open handling
- Local HTTPS
- Local network access
- Tunnel forwarding
- Top-level
proxy.hostrouting
A project with one service has an implicit primary service.
If a multi-service project uses a main public endpoint, set primary: true on one service.
These options require one primary service in a multi-service project:
--port--https--network--tunnel
More than one primary service causes an error.
Separate public and child ports
Without HTTPS, --port requests the assigned port for the primary child service:
lpm dev --port 4000With HTTPS, the same value belongs to the browser-facing TLS frontend:
lpm dev --https --port 4000In the HTTPS example, the browser connects to port 4000. The primary child receives a separate managed port and continues to use plain HTTP.
The local proxy has its own public listener. A tunnel has a public URL that forwards to the verified primary child endpoint.
See Local HTTPS, lpm proxy, and Tunneling for those public frontends.
Connect services with environment variables
LPM CLI gives each service the final ports of its peers:
For service "web":
API_PORT=4000
API_URL=http://localhost:4000
DB_PORT=5432
DB_URL=http://localhost:5432
For service "api":
WEB_PORT=5173
WEB_URL=http://localhost:5173
DB_PORT=5432
DB_URL=http://localhost:5432The variable pattern is {SERVICE_NAME}_PORT and {SERVICE_NAME}_URL. LPM CLI converts the service name to uppercase and changes hyphens to underscores.
For example, a service named my-api becomes MY_API_PORT and MY_API_URL.
A service does not receive variables for itself. It receives variables only for peers with an assigned service port.
The values use the final assigned ports. A replacement port appears in these variables automatically.
Peer URLs continue to use http://localhost:<port> during local HTTPS. LPM CLI terminates browser HTTPS in front of the plain HTTP services.
Service-specific env values are also available:
{
"services": {
"api": {
"command": "node api.js",
"port": 4000,
"env": {
"LOG_LEVEL": "debug"
}
}
}
}Wait for service readiness
For a managed port, LPM CLI waits for a new listener that belongs to the launched service process.
This ownership rule prevents an unrelated process from satisfying the managed-port readiness step.
You can add an application readiness target:
| Configuration | Behavior |
|---|---|
Managed port only | Wait for the assigned listener and make sure that the launched process owns it. |
readyPort | Poll the separate TCP port after managed-listener ownership. |
readyUrl | Poll the URL until it returns a 2xx response. |
| No managed port or readiness target | Use a short launch grace period. |
If both readyUrl and a distinct readyPort exist, readyUrl is the application readiness target.
LPM CLI also recognizes advertised loopback URLs in service output. It preserves the advertised scheme and base path after it verifies the listener.
The readiness wait uses readyTimeout. The default is 30 seconds.
If a service fails a readiness check, LPM CLI stops the initial startup.
LPM CLI terminates each service that it started and returns an error.
If a dependsOn service fails readiness, LPM CLI does not start the dependent service.
Start services in dependency order
dependsOn defines the initial startup order:
{
"services": {
"db": { "command": "docker compose up postgres", "port": 5432 },
"api": { "command": "node api.js", "port": 4000, "dependsOn": ["db"] },
"web": { "command": "vite", "port": 5173, "dependsOn": ["api"] }
}
}LPM CLI reports an error for a missing dependency or a dependency cycle.
Independent branches start together. A dependent service starts only after all its declared dependencies are ready.
During shutdown, LPM CLI stops dependents before their dependencies. This order gives dependent services time to close active connections.
Restart crashed services
Set restart: true for a service that must restart after a non-zero exit:
{
"services": {
"worker": {
"command": "node worker.js",
"restart": true
}
}
}LPM CLI uses increasing restart delays from 1 second to 30 seconds. It stops after 10 failed restart attempts.
If the service stays active for more than 60 seconds, the next crash starts a new retry sequence.
Before LPM CLI restarts a service, each direct dependsOn service must be ready.
If a dependency restarts, LPM CLI stops its active transitive dependents in reverse dependency order. These dependents wait for recovery.
After the dependency is ready, LPM CLI restarts the waiting dependents in dependency order. Each dependency level must become ready before the next level starts.
If a restarted service fails readiness, LPM CLI stops that process. Then it schedules another attempt with the existing restart delay.
Dashboard restarts use the same dependency rules. A service that you stop from the dashboard remains stopped during dependency recovery.
If a dependency exits or cannot restart, LPM CLI stops its transitive dependents. LPM CLI does not resume them automatically.
Inspect port state
Use the complete command syntax:
lpm ports [action] [target]Common read-only commands:
lpm ports # show ports for this project
lpm ports all # show visible TCP listeners
lpm ports inspect 5173 # inspect one portReset machine-local replacements:
lpm ports resetFor an explicit service port, lpm ports reports the configured port. It does not replace that row with a saved conflict port.
Use the lpm dev startup output or lpm ports all to find the active replacement. A host-only service can appear with its saved assigned port.
See lpm ports for inspection output, process termination, JSON output, and platform differences.
Common problems
lpm dev uses a different port on every restart
Run lpm ports all to find listeners near the requested port. Concurrent development processes can make LPM CLI select another replacement.
A service stays on its replacement after the preferred port is free
Clear the project replacement, then restart the development run:
lpm ports reset
lpm devlpm ports shows the preferred port, but lpm dev uses another port
This behavior applies to explicit service ports. The project table shows the configured value, while startup uses the saved replacement.
Use the startup output or lpm ports all to find the active listener.
The browser port is not the child port
HTTPS, proxy, and network frontends can use separate public ports. Read the URL that lpm dev prints.
Startup stops after a readiness failure
An ownership, readyPort, or readyUrl failure stops the initial startup.
Read the service output. Then correct the readiness target or increase readyTimeout.
A peer variable is missing
Make sure that the peer has an assigned service port. A readyPort without port, primary, or host does not create peer variables.
See also
lpm ports— inspect listeners and reset saved replacementslpm dev— run the development service graphlpm.jsonservices — configure services and readinesslpm proxy— route friendly hostnames to assigned ports- Local HTTPS — separate the browser TLS port from child ports
- Tunneling — forward a public URL to the primary service