Configuration

Bare portless works out of the box. It runs the "dev" script from package.json through the proxy, inferring the app name from the package name, git root, or directory:

portless        # runs "dev" script, https://<project>.localhost

portless.json

Use an optional portless.json to override defaults:

{ "name": "myapp" }
portless        # runs "dev" script, https://myapp.localhost

The name is inferred from package.json if not set in config. The script defaults to "dev".

Fields

FieldTypeDefaultDescription
namestringinferredBase app name. Worktree prefix still applies.
scriptstring"dev"Name of a package.json script to run.
appPortnumberautoFixed port for the child process.
proxybooleanauto

Whether to route through the proxy. Auto-detected from the command; set false for non-server scripts.

appsobjectOverrides for workspace packages, keyed by relative path.
turbobooleantrueSet false to use direct spawning instead of turborepo in multi-app mode.

Each apps entry has the same shape (name, script, appPort, proxy). When apps is present, top-level fields apply only in single-app mode.

package.json "portless" key

Instead of a separate portless.json, you can add a "portless" key to your package.json. A string value is shorthand for setting the name:

{
  "name": "@myorg/web",
  "portless": "myapp"
}

An object supports all per-app fields (name, script, appPort, proxy):

{
  "name": "@myorg/web",
  "portless": { "name": "myapp", "script": "dev:app" }
}

The package.json "portless" key takes precedence over portless.json app entries but is overridden by CLI flags.

Monorepo

One portless.json at the repo root covers all workspace packages. Portless discovers packages from pnpm-workspace.yaml, or the "workspaces" field in package.json (npm, yarn, bun):

{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}
portless                  # from repo root: start all packages with a "dev" script
cd apps/web && portless   # start just one package
portless --script start   # run "start" instead of "dev"

The apps map is optional and only needed for overrides. Packages not listed still auto-discover with names inferred from their package.json.

Without an apps map, hostnames follow the <package>.<project>.localhost convention. The project name comes from the most common npm scope across workspace packages (e.g. @myorg/web and @myorg/api produce project name myorg), falling back to the workspace root directory name. If a package's short name matches the project name, it gets the bare <project>.localhost without duplication.

Turborepo

For turborepo projects, use portless as the dev script and the real command in a separate script:

{
  "scripts": {
    "dev": "portless",
    "dev:app": "next dev"
  },
  "portless": { "name": "myapp", "script": "dev:app" }
}

pnpm dev at the root runs turbo, which runs portless in each package. Portless detects the package manager and runs pnpm run dev:app through the proxy. No turbo.json changes are needed.

People without portless installed can run pnpm run dev:app directly.

Precedence

Closest config wins, like Prettier and ESLint:

For name: CLI --name flag > package.json "portless" key > portless.json app entry > package.json inference.

For script: CLI --script flag > package.json "portless" key > portless.json app entry > default "dev".

For appPort: CLI --app-port flag > PORTLESS_APP_PORT env var > package.json "portless" key > portless.json app entry > auto-assigned.

Environment variables

VariableDescriptionDefault
PORTLESS_PORTProxy port443 (HTTPS) / 80 (HTTP)
PORTLESS_HTTPSHTTPS on by default; set to 0 to disable (same as --no-tls)on
PORTLESS_LANSet to 1 to always enable LAN mode (mDNS .local domains)off
PORTLESS_TLDUse a custom TLD instead of .localhost (e.g. test)localhost
PORTLESS_APP_PORTUse a fixed port for the app (skip auto-assignment)random 4000--4999
PORTLESS_SYNC_HOSTSSet to 0 to disable auto-sync of /etc/hostson
PORTLESS_STATE_DIROverride the state directory~/.portless
PORTLESSSet to 0 to bypass the proxyenabled

State directory

Portless stores state (routes, PID file, port file, TLS marker) in ~/.portless on all platforms. Override with PORTLESS_STATE_DIR.

State files

FilePurpose
routes.jsonMaps hostnames to ports
routes.lockPrevents concurrent writes
proxy.pidPID of the running proxy
proxy.portPort the proxy is listening on
proxy.logProxy daemon log output
proxy.lanRemembers LAN mode and stores the last known LAN IP

Port assignment

Apps get a random port in the 4000--4999 range. Portless sets PORT and usually HOST before running your command. Most frameworks respect PORT automatically. For frameworks that ignore it (Vite, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right --port flag and, when needed, a matching --host flag.