PortOS uses a contiguous port allocation scheme to make it easy to understand which ports are in use and which are available.
- Contiguous Ranges: Each app should use a contiguous block of ports
- Labeled Ports: Use the
portsobject inecosystem.config.cjsto define all ports with descriptive labels - No Gaps: Avoid leaving gaps between port allocations within an app
Common port labels:
api- REST API serverui- Web UI / frontendcdp- Chrome DevTools Protocolhealth- Health check endpointws- WebSocket server
| Port | Process | Label | Description |
|---|---|---|---|
| 5553 | portos-server | api-local | Loopback-only HTTP mirror of the API (only listens when HTTPS is active on 5555). Lets http://localhost:5553 work without cert warnings. Override with PORTOS_HTTP_PORT. |
| 5554 | portos-client | ui | Vite dev server (React UI) — only present in npm run dev; npm start serves the built client from :5555 directly. |
| 5555 | portos-server | api | Main API server — always the user-facing port. Switches between HTTP and HTTPS based on whether data/certs/{cert,key}.pem exists. |
| 5556 | portos-browser | cdp | Chrome DevTools Protocol |
| 5557 | portos-browser | health | Browser health check API |
| 5558 | portos-cos | api | CoS Agent Runner (isolated process) |
| 5559 | portos-autofixer | api | Autofixer daemon API |
| 5560 | portos-autofixer-ui | ui | Autofixer web UI |
| 5561 | portos-db | db | PostgreSQL Docker container (native mode uses system pg on 5432) |
These three ports are easy to confuse, so:
┌─ :5555 ─ HTTPS app (Tailscale cert) ← always user-facing
remote browser ──────────┤
└─ :5555 ─ HTTP app (no cert) ← always user-facing
local scripts / curl ────── :5553 ─ HTTP loopback mirror (HTTPS mode only, 127.0.0.1)
vite dev (npm run dev) ──── :5554 ─ Vite dev server (dev only, separate process)
Rules of thumb:
:5555is the only port a remote user ever needs. The scheme (HTTP vs HTTPS) flips based on whether a TLS cert is provisioned (npm run setup:cert); the port number does not.:5553is a convenience for local terminals. When HTTPS is on,https://localhost:5555would trip a cert warning (the cert covers<machine>.<tailnet>.ts.net, notlocalhost). The loopback HTTP mirror on:5553lets curl/scripts skip TLS entirely. It binds to127.0.0.1only — never reachable over the network.:5554isvite devonly. Innpm run dev, Vite serves the React UI from:5554and proxies/apicalls to:5555. Innpm start(production), the React build is served from:5555itself;:5554is unused.
Define all ports in a top-level PORTS object as the single source of truth:
// =============================================================================
// Port Configuration - All ports defined here as single source of truth
// =============================================================================
const PORTS = {
API: 5570, // REST API server
UI: 5571, // Web UI
CDP: 5572 // Chrome DevTools Protocol
};
module.exports = {
PORTS, // Export for other configs to reference
apps: [
{
name: 'my-api',
script: 'server.js',
env: {
PORT: PORTS.API
}
},
{
name: 'my-ui',
script: 'node_modules/.bin/vite',
args: `--port ${PORTS.UI}`,
env: {
VITE_PORT: PORTS.UI
}
}
]
};- Single Source of Truth: Each port defined once
- Importable: Other configs can
require('./ecosystem.config.cjs').PORTS - Clear Comments: Document what each port is for
- DRY: No duplication between
portsobject andenvvars
PortOS automatically detects ports from env vars:
PORT→ labeled asapi(oruifor-ui/-clientprocesses,healthfor-browserprocesses with CDP)CDP_PORT→ labeled ascdpVITE_PORT→ labeled asui--portin args → labeled asui
- Check Available Ports: Use PortOS apps list to see which ports are in use
- Pick a Contiguous Range: Choose a starting port and allocate contiguously
- Define PORTS Object: Always define ports in a top-level
PORTSconstant - Avoid Common Ports: Stay away from well-known ports (80, 443, 3000, 8080, etc.)
| Range | Purpose |
|---|---|
| 5554-5560 | PortOS core services |
| 5561-5569 | Reserved for PortOS extensions |
| 5570-5599 | User applications |
The PortOS apps list shows all ports for each process:
- Single port:
process-name:5555 - Multiple ports:
process-name (cdp:5556,health:5557)
Use the API to get detailed port information:
curl http://localhost:5555/api/apps | jq '.[].processes'