Claude
Skills
Sign in
Back

portless

Included with Lifetime
$97 forever

Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting port/proxy issues.

General

What this skill does


# Portless

Replace port numbers with stable, named .localhost URLs. For humans and agents.

## Why portless

- **Port conflicts**: `EADDRINUSE` when two projects default to the same port
- **Memorizing ports**: which app is on 3001 vs 8080?
- **Refreshing shows the wrong app**: stop one server, start another on the same port, stale tab shows wrong content
- **Monorepo multiplier**: every problem scales with each service in the repo
- **Agents test the wrong port**: AI agents guess or hardcode the wrong port
- **Cookie/storage clashes**: cookies on `localhost` bleed across apps; localStorage lost when ports shift
- **Hardcoded ports in config**: CORS allowlists, OAuth redirects, `.env` files break when ports change
- **Sharing URLs with teammates**: "what port is that on?" becomes a Slack question
- **Browser history is useless**: `localhost:3000` history is a mix of unrelated projects

## Installation

Install globally (recommended) or as a project dev dependency. Do NOT use `npx` or `pnpm dlx` for one-off execution.

```bash
# Global (available everywhere)
npm install -g portless

# Or per-project dev dependency
npm install -D portless
```

When installed per-project, invoke via package.json scripts or `npx portless` (since the package is local, npx will not download anything).

## Quick Start

```bash
# Install globally (or add -D to a project)
npm install -g portless

# Run your app (auto-starts the HTTPS proxy on port 443)
portless run next dev
# -> https://<project>.localhost

# Or with an explicit name
portless myapp next dev
# -> https://myapp.localhost
```

The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`. Auto-start reuses the configuration (port, TLS, TLD) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars always take priority.

In non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting. Task runners like turborepo should pre-start the proxy.

## Integration Patterns

### Zero-config (recommended)

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:

```bash
portless        # -> runs "dev" script, https://<project>.localhost
pnpm dev        # -> works without portless, plain "next dev"
```

Use an optional `portless.json` to override defaults (name, script, port):

```json
{ "name": "myapp" }
```

```bash
portless        # -> runs "dev" script, https://myapp.localhost
```

### Monorepo

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

```json
{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}
```

```bash
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 provides name overrides. Unlisted packages auto-discover with inferred names.

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

### Turborepo

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

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

`pnpm dev` runs turbo, which runs `portless` in each package. Portless detects the package manager and runs `pnpm run dev:app` through the proxy.

### package.json scripts

You can still use portless directly in scripts:

```json
{
  "scripts": {
    "dev": "portless run next dev"
  }
}
```

The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`.

### Multi-app setups with subdomains

```bash
portless myapp next dev          # https://myapp.localhost
portless api.myapp pnpm start    # https://api.myapp.localhost
portless docs.myapp next dev     # https://docs.myapp.localhost
```

By default, only explicitly registered subdomains are routed (strict mode). Start the proxy with `--wildcard` to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app). Exact matches always take priority over wildcards.

### Git worktrees

`portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain prefix so each worktree gets a unique URL:

```bash
# Main worktree (no prefix)
portless run next dev   # -> https://myapp.localhost

# Linked worktree on branch "fix-ui"
portless run next dev   # -> https://fix-ui.myapp.localhost
```

No config changes needed. Put `portless run` in `package.json` once and it works in all worktrees.

### Bypassing portless

Set `PORTLESS=0` to run the command directly without the proxy:

```bash
PORTLESS=0 pnpm dev   # Bypasses proxy, uses default port
```

## How It Works

1. `portless proxy start` starts an HTTPS reverse proxy on port 443 as a background daemon. Auto-elevates with sudo on macOS/Linux; falls back to port 1355 if sudo is unavailable. Use `--no-tls` for plain HTTP on port 80. Configurable with `-p` / `--port` or the `PORTLESS_PORT` env var. The proxy also auto-starts when you run an app.
2. `portless <name> <cmd>` assigns a random free port (4000-4999) via the `PORT` env var and registers the app with the proxy
3. The browser hits `https://<name>.localhost`; the proxy forwards to the app's assigned port

`.localhost` domains resolve to `127.0.0.1` natively in Chrome, Firefox, and Edge. Safari relies on the system DNS resolver, which may not handle `.localhost` subdomains on all configurations. Run `portless hosts sync` to add entries to `/etc/hosts` if needed.

Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag.

### State directory

Portless stores its state (routes, PID file, port file) in `~/.portless`. Override with the `PORTLESS_STATE_DIR` environment variable.

### Environment variables

| Variable              | Description                                                                 |
| --------------------- | --------------------------------------------------------------------------- |
| `PORTLESS_PORT`       | Override the default proxy port (default: 443 with HTTPS, 80 without)       |
| `PORTLESS_APP_PORT`   | Use a fixed port for the app (skip auto-assignment)                         |
| `PORTLESS_HTTPS`      | HTTPS on by default; set to `0` to disable (same as `--no-tls`)             |
| `PORTLESS_LAN`        | Set to `1` to always enable LAN mode (auto-detects LAN IP)                  |
| `PORTLESS_LAN_IP`     | Pin a specific LAN IP for LAN mode                                          |
| `PORTLESS_TLD`        | Use a custom TLD instead of localhost (e.g. test)                           |
| `PORTLESS_WILDCARD`   | Set to `1` to allow unregistered subdomains to fall back to parent          |
| `PORTLESS_SYNC_HOSTS` | Set to `0` to disable auto-sync of /etc/hosts (on by default)               |
| `PORTLESS_TAILSCALE`  | Set to `1` to share apps on your Tailscale network (same as `--tailscale`)  |
| `PORTLESS_FUNNEL`     | Set to `1` to share apps publicly via Tailscale Funnel (same as `--funnel`) |
| `PORTLESS_NGROK`      | Set to `1` to share apps publicly via ngrok (same as `--ngrok`)             |
Files: 1
Size: 23.7 KB
Complexity: 29/100
Category: General

Related in General