Skip to content
Back to skills

Portless Local

CSecurity

Replace port numbers with stable named .localhost URLs for local development

  • 123 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
developmentrustgoshellbashreactangularnextjsnodeexpressdocker

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

C71/100
  • criticalAccesses sensitive system or user directories
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 7, 2026

npx -y skills add jellydn/my-ai-tools --skill portless-local --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Portless Local?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Portless Local
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/jellydn-portless-local/badge)](https://www.skillsdirectory.com/skills/jellydn-portless-local)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: portless-local
description: "Replace port numbers with stable named .localhost URLs for local development"
license: MIT
compatibility: cline, claude, opencode, amp, codex, gemini, cursor, pi
hint: Use when you want clean, named URLs for local development instead of remembering port numbers
user-invocable: true
disable-model-invocation: true
metadata:
  audience: all
  workflow: development
---

# Portless - Named .localhost URLs

Replace port numbers with stable, named `.localhost` URLs for local development. For humans and agents.

> **Note:** The portless CLI enables HTTPS on port 443 by default (`https://myapp.localhost`). Pass `--no-tls` or set `PORTLESS_HTTPS=0` for plain HTTP. Keep HTTPS when the app needs OAuth, secure cookies, or HTTP/2.

## Why Portless?

Local dev with port numbers is fragile. Portless fixes that by giving each dev server a stable, named `.localhost` URL.

| Problem                    | With Ports                                              | With Portless                                   |
| -------------------------- | ------------------------------------------------------- | ----------------------------------------------- |
| **Port conflicts**         | Two projects on :3000 = EADDRINUSE                      | Auto-assigned ports, named URLs - no collisions |
| **Memorizing ports**       | "Was the API on 3001 or 8080?"                          | Always `https://api.localhost`                   |
| **Wrong app on refresh**   | Stop one server, start another on same port = confusion | Named URLs eliminate this                       |
| **Monorepo chaos**         | Every service needs a unique port                       | Distinct hostnames for each service             |
| **Agent confusion**        | AI agents guess/hardcode wrong ports                    | `https://myapp.localhost` is deterministic       |
| **Cookie/storage clashes** | Cookies bleed across ports on localhost                 | Each `.localhost` subdomain gets its own scope  |
| **Hardcoded config**       | CORS, OAuth, .env break when ports change               | URLs are stable across restarts                 |
| **Sharing URLs**           | "What port is that on?" in Slack                        | Everyone uses the same named URL                |
| **Browser history**        | `localhost:3000` history is a jumble                    | Named URLs keep things organized                |

## Installation

```bash
# Global (recommended)
npm install -g portless

# Or as a project dev dependency
npm install -D portless
```

> **Note:** portless is pre-1.0. When installed per-project, different contributors may run different versions.

## Usage

Invoke via skill command or use CLI directly:

```bash
# Via skill command
/portless-local <NAME> <COMMAND> [OPTIONS]

# Or use CLI directly
portless <NAME> <COMMAND> [OPTIONS]
```

## Commands

### Run an App

```bash
portless run [--name <name>] <cmd> [args...]   # Infers name from package.json, git root, or directory
portless <name> <cmd> [args...]                # Explicit name, no inference
```

`portless run` infers the project name from package.json, git root, or directory name. Use `--name` to override the inferred name while still applying worktree prefixes.

| Flag                  | Description                                                                                         |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| `--name <name>`       | Override the inferred base name (worktree prefix still applies). Only for `portless run`.           |
| `--app-port <number>` | Use a fixed port for the app instead of auto-assignment. Also configurable via `PORTLESS_APP_PORT`. |
| `--force`             | Override an existing route registered by another process                                            |

**Examples:**

```bash
portless run next dev                    # Infer name from project
portless run --name myapp next dev       # Override inferred name
portless myapp next dev                  # Explicit name
portless api pnpm start                  # API service
portless docs.myapp next dev             # Subdomain
```

### Get a Service URL

```bash
portless get <name>
```

Print the URL for a service. Useful for wiring services together in scripts or env vars:

```bash
BACKEND_URL=$(portless get backend)
```

Applies worktree prefix detection by default. Use `--no-worktree` to skip it.

### Alias (Static Routes)

```bash
portless alias <name> <port>              # Register a static route
portless alias <name> <port> --force     # Force override existing
portless alias --remove <name>            # Remove the alias
```

Register a route for a service not managed by portless (e.g. a Docker container). Aliases persist across stale-route cleanup.

```bash
portless alias my-postgres 5432     # -> https://my-postgres.localhost
portless alias redis 6379           # -> https://redis.localhost
portless alias --remove my-postgres # Remove the alias
```

### List Routes

```bash
portless list
```

Shows active routes and their assigned ports.

### Trust the CA

```bash
portless trust
```

Adds the portless certificate authority to your system trust store. Required once for HTTPS with auto-generated certs.

If you skipped the trust prompt on first run, run `portless trust` to add the CA later.

### HTTPS & HTTP/2

The CLI enables HTTPS on port 443 by default. That covers OAuth callbacks, secure cookies, and HTTP/2. Pass `--no-tls` or set `PORTLESS_HTTPS=0` only when the app should stay on plain HTTP.

When HTTPS stays on, the first run may generate a local CA and server certificates. Run `portless trust` if the host or browser must trust that certificate.

**Custom certificates:** Use your own certs (e.g., from mkcert):

```bash
portless proxy start --cert ./cert.pem --key ./key.pem
```

**Disable HTTPS:** HTTPS on port 443 is the CLI default. Pass `--no-tls` or `PORTLESS_HTTPS=0` for plain HTTP on port 80:

```bash
portless proxy start --no-tls
portless myapp next dev --no-tls
```

### Clean Up

```bash
portless clean
```

Stops the proxy, removes the CA from OS trust store, deletes allowlisted files under `~/.portless`, the system state directory, and removes the portless block from `/etc/hosts`. May prompt for elevated privileges.

### Proxy Control

#### Start Proxy

```bash
portless proxy start
```

| Flag                  | Description                                                                |
| --------------------- | -------------------------------------------------------------------------- |
| `-p, --port <number>` | Proxy port (default: 443, or 80 with `--no-tls`).              |
| `--no-tls`            | Disable HTTPS (plain HTTP on port 80).                         |
| `--https`             | Enable HTTPS (CLI default; kept for compatibility).            |
| `--lan`               | Enable LAN mode (mDNS `.local` domains for real device testing)            |
| `--ip <address>`      | Override auto-detected LAN IP (use with `--lan`)                           |
| `--tld <tld>`         | Use a custom TLD instead of `.localhost` (e.g. `.test`)                    |
| `--cert <path>`       | Custom TLS certificate                                                     |
| `--key <path>`        | Custom TLS private key                                                     |
| `--foreground`        | Run in foreground instead of daemon mode                                   |

#### Stop Proxy

```bash
portless proxy stop
```

### LAN Mode

Access services from phones and other devices on the same WiFi via mDNS (`.local` domains):

```bash
portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42   # Manual IP override
```

Make it permanent by adding `export PORTLESS_LAN=1` to your shell profile. Portless also remembers LAN mode via `proxy.lan`, so a stopped LAN proxy starts in LAN mode again.

**Framework notes for LAN:**

- **Next.js:** Add `allowedDevOrigins: ['myapp.local', '*.myapp.local']` to `next.config.js`
- **Vite / React Router / SvelteKit / Astro:** Handled automatically via `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS`
- **Expo / React Native:** Add `NSAllowsLocalNetworking` to `app.json` for iOS ATS

### Hosts

```bash
portless hosts sync     # Add current routes to /etc/hosts
portless hosts clean    # Remove portless entries from /etc/hosts
```

Auto-sync is on by default. Set `PORTLESS_SYNC_HOSTS=0` to disable.

### Bypass Portless

```bash
PORTLESS=0 pnpm dev
```

Runs the command directly without the proxy.

### Info

```bash
portless --help
portless --version
```

## Common Use Cases

### 1. Basic Development Server

```bash
# Next.js
portless myapp next dev
# -> https://myapp.localhost

# Vite (auto-detected, --port injected)
portless myapp vite dev
# -> https://myapp.localhost

# Express
portless api node server.js
# -> https://api.localhost
```

### 2. Multiple Services with Subdomains

```bash
# API service
portless api.myapp pnpm start
# -> https://api.myapp.localhost

# Documentation
portless docs.myapp next dev
# -> https://docs.myapp.localhost

# Admin dashboard
portless admin.myapp npm run dev
# -> https://admin.myapp.localhost
```

### 3. Use in package.json

```json
{
	"scripts": {
		"dev": "portless myapp next dev",
		"dev:http": "portless myapp next dev --no-tls"
	}
}
```

### 4. Git Worktree Support

`portless run` auto-detects git worktrees. The branch name is prepended as a subdomain:

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

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

Put `portless run` in your package.json once and it works everywhere - no collisions, no `--force`.

### 5. Custom TLD

```bash
# Use .test TLD instead of .localhost
portless proxy start --tld test
portless myapp next dev
# -> https://myapp.test
```

Recommended TLDs:

- `.localhost` - Default, auto-resolves to 127.0.0.1 in most browsers
- `.test` - IANA-reserved, no collision risk (recommended)
- **Avoid:** `.local` (conflicts with mDNS/Bonjour), `.dev` (Google-owned, forces HTTPS via HSTS)

### 6. Static Aliases for External Services

```bash
# Docker container running Postgres
portless alias my-postgres 5432
# -> https://my-postgres.localhost

# Redis server
portless alias redis 6379
# -> https://redis.localhost
```

### 7. Wire Services Together

```bash
# Get backend URL for frontend env
BACKEND_URL=$(portless get backend)
echo "VITE_API_URL=$BACKEND_URL" > .env.local
portless frontend vite dev
```

## How It Works

```
Browser (myapp.localhost) -> HTTPS Proxy (port 443) -> App (random port 4000-4999)
```

1. Portless runs an HTTPS reverse proxy on port 443 (or HTTP on port 80 with `--no-tls`)
2. Each app registers a route mapping hostname to assigned port
3. Requests to `https://<name>.localhost` are proxied to the app
4. Default HTTPS: auto-generates a local CA. Run `portless trust` when the host or browser must trust it
5. Auto-elevates with sudo on macOS/Linux for port binding

## Framework Support

Portless auto-detects and configures:

| Framework    | Support     | Notes                           |
| ------------ | ----------- | ------------------------------- |
| Next.js      | ✅ Native   | Respects PORT env var           |
| Express      | ✅ Native   | Respects PORT env var           |
| Nuxt         | ✅ Native   | Respects PORT env var           |
| Vite         | ✅ Injected | Auto-adds `--port` flag         |
| Astro        | ✅ Injected | Auto-adds `--port` flag         |
| React Router | ✅ Injected | Auto-adds `--port` flag         |
| Angular      | ✅ Injected | Auto-adds `--port` and `--host` |
| Expo         | ✅ Injected | Auto-adds `--port` and `--host` |
| React Native | ✅ Injected | Auto-adds `--port` and `--host` |

## Configuration

Portless is configured through environment variables. No config files needed.

### Environment Variables

| Variable              | Description                                                     | Default                 |
| --------------------- | --------------------------------------------------------------- | ----------------------- |
| `PORTLESS_PORT`       | Proxy port                                                      | 443; 80 with `--no-tls` |
| `PORTLESS_HTTPS`      | Set to `0` to disable HTTPS (same as `--no-tls`)                | on                      |
| `PORTLESS_LAN`        | Set to `1` to always enable LAN mode (mDNS `.local` domains)    | off                     |
| `PORTLESS_TLD`        | Use a custom TLD instead of `.localhost` (e.g. `test`)          | localhost               |
| `PORTLESS_APP_PORT`   | Use a fixed port for the app (skip auto-assignment)             | random 4000-4999        |
| `PORTLESS_SYNC_HOSTS` | Set to `0` to disable auto-sync of `/etc/hosts`                 | on                      |
| `PORTLESS_STATE_DIR`  | Override the state directory                                    | see below               |
| `PORTLESS`            | Set to `0` to bypass the proxy                                  | enabled                 |

### State Directory

Portless stores state (routes, PID file, port file, TLS marker) in a directory that depends on the proxy port:

| Condition                           | Path            |
| ----------------------------------- | --------------- |
| Port below 1024 (sudo, macOS/Linux) | `/tmp/portless` |
| Port 1024+ (no sudo)                | `~/.portless`   |
| Windows (any port)                  | `~/.portless`   |

Override with `PORTLESS_STATE_DIR`.

### State Files

| File          | Purpose                                             |
| ------------- | --------------------------------------------------- |
| `routes.json` | Maps hostnames to ports                             |
| `routes.lock` | Prevents concurrent writes                          |
| `proxy.pid`   | PID of the running proxy                            |
| `proxy.port`  | Port the proxy is listening on                      |
| `proxy.log`   | Proxy daemon log output                             |
| `proxy.lan`   | Remembers 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.

## Troubleshooting

### Port 443 permission denied

```bash
# Portless auto-elevates with sudo, but if it fails:
sudo portless proxy start

# Or use HTTP mode on a different port
portless myapp next dev --no-tls -p 8080
```

### Certificate warning

Trust the local CA on first run. Run `portless trust` if needed.

### Name collision

```bash
# Each worktree gets unique subdomain automatically
# Or use different names:
portless myapp-v2 next dev
```

## Comparison

| Tool         | Type          | URLs                              | Use Case             |
| ------------ | ------------- | --------------------------------- | -------------------- |
| **portless** | Local proxy   | `https://myapp.localhost`          | Clean local dev URLs |
| ngrok        | Public tunnel | `https://random.ngrok.io`         | Share with others    |
| cloudflared  | Public tunnel | `https://myapp.trycloudflare.com` | Share with others    |

## Related

- [portless.sh](https://portless.sh/) - Official documentation
- [vercel-labs/portless](https://github.com/vercel-labs/portless) - Official skill for Claude Code

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…