OpenSource-Hub

portless

CLI 도구

vercel-labs/portless

안정적인 로컬 도메인 이름으로 포트 번호를 대체하여 개발합니다.

개요

Portless는 로컬 개발 서버에 myapp.localhost와 같은 안정적인 HTTPS 도메인을 제공하여 임의의 포트를 대체하는 명령줄 도구입니다. 프록시를 자동으로 시작하고, TLS 인증서 신뢰를 처리하며, 일반적인 프레임워크에 포트 인수를 주입합니다. 서브도메인, Monorepo, Turbo 파이프라인 및 Git Worktree를 지원합니다.

README 미리보기

# portless\n\nReplace port numbers with stable, named .localhost URLs for local development. For humans and agents.\n\n```diff\n- "dev": "next dev"                  # http://localhost:3000\n+ "dev": "portless run next dev"     # https://myapp.localhost\n```\n\n## Install\n\n**Global (recommended):**\n\n```bash\nnpm install -g portless\n```\n\n**Or as a project dev dependency:**\n\n```bash\nnpm install -D portless\n```\n\n> portless is pre-1.0. When installed per-project, different contributors may run different versions. The state directory format may change between releases, which can require re-running `portless trust`.\n\n## Run your app\n\n```bash\nportless myapp next dev\n# -> https://myapp.localhost\n```\n\nHTTPS with HTTP/2 is enabled by default. On first run, portless generates a local CA, trusts it, and binds port 443 (auto-elevates with sudo on macOS/Linux). Use `--no-tls` for plain HTTP.\n\nThe proxy auto-starts when you run an app. A random port (4000-4999) is assigned via the `PORT` environment variable. Most frameworks (Next.js, Express, Nuxt, etc.) respect this automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag. Injection reaches through a package script whose command starts with the framework or a known runner (`"dev": "vite"`, `"dev": "bunx vite"`). Only the framework's server commands get the flags (`dev`, `serve`, `preview`, `start`, a bare `vite`, or `vite [root]`); a command that does not serve, such as `vite build`, `vite optimize`, `vp test` or `astro check`, rejects them and is left alone. Expo connection modes (`--localhost`, `--lan`, `--tunnel`) are preserved while the assigned port is still injected. A script portless cannot classify is left alone too: a flag before the subcommand on a CLI whose flag grammar it does not track (`vp --mode dev build`). Portless also leaves a script alone w

FAQ (5)

문제 해결
keep-alive 요청 후 백엔드가 재시작될 때 Node.js 프록시에서 처리되지 않은 'read ECONNRESET' 크래시를 어떻게 수정하나요?

이 문제는 portless 0.10.3–0.15.6에 영향을 미칩니다. 프록시는 클라이언트의 Connection: keep-alive 헤더를 백엔드로 전달하지만, 응답이 완료된 후에는 백엔드 소켓에 에이전트나 error 리스너가 없습니다. 백엔드가 재시작되면서 소켓을 리셋하면 Node는 처리되지 않은 ECONNRESET을 발생시키고 종료됩니다.

http.request()를 호출하기 전에 hop-by-hop 헤더를 제거하여 일반 HTTP 프록시 경로를 수정하세요:

for (const name of String(proxyReqHeaders.connection || '').split(',')) {
  const header = name.trim().toLowerCase();
  if (header) delete proxyReqHeaders[header];
}

for (const header of HOP_BY_HOP_HEADERS) {
  delete proxyReqHeaders[header];
}

일반적인 hop-by-hop 헤더는 다음과 같습니다:

const HOP_BY_HOP_HEADERS = [
  'connection',
  'keep-alive',
  'proxy-authenticate',
  'proxy-authorization',
  'te',
  'trailer',
  'transfer-encoding',
  'upgrade',
];

WebSocket 업그레이드 경로에는 이를 적용하지 마세요. 해당 경로는 의도적으로 Connection: Upgrade를 전송하며 이미 소켓 error 리스너를 연결하고 있습니다.

임시 해결 방법으로, createLoopbackConnection()이 반환하는 소켓에 no-op 에러 핸들러를 연결하세요:

createConnection: () => {
  const socket = createLoopbackConnection(route.port);
  socket.on('error', () => {});
  return socket;
}

issue #434에 대한 수정이 포함된 릴리스가 제공되면 portless를 해당 릴리스로 업그레이드하세요.

원본 Issue #434
문제 해결
Portless 와일드카드 서브도메인이 잘못된 상위 앱으로 라우팅되는 문제는 어떻게 해결하나요?

PORTLESS_WILDCARD=1이 설정되면 Portless 0.15.3/0.15.5는 와일드카드 폴백에 routes.find()를 사용하므로, 가장 구체적인 항목 대신 routes.json에서 첫 번째로 일치하는 부모가 선택됩니다. findRoute를 수정하여 가장 길게 일치하는 부모 호스트명을 선택하도록 하십시오:

routes
  .filter((r) => hostname.endsWith("." + r.hostname.toLowerCase()))
  .sort((a, b) => b.hostname.length - a.hostname.length)[0]

예를 들어, admin.api.acme.localhost는 이제 acme.localhost보다 먼저 api.acme.localhost와 일치합니다. 임시 해결책으로 더 구체적인 라우트를 routes.json의 앞쪽에 배치할 수 있지만, 이는 순서에 의존하며 패치보다 신뢰성이 낮습니다. 수정된 릴리스에는 "fix(proxy): pick the most specific parent route in wildcard fallback"이라는 제목의 PR이 포함되어야 합니다.

원본 Issue #380
문제 해결
Portless가 /etc/hosts를 덮어쓰고 localhost 및 기타 시스템 항목을 제거하는 문제를 해결하는 방법은?

PR #424가 포함된 Portless 릴리스로 업그레이드하세요(issue #419 수정). 영향을 받는 버전에서는 /etc/hosts를 읽을 수 없을 때 readHostsFile()이 빈 문자열을 반환했기 때문에 syncHostsFile()이 Portless가 관리하는 블록만 기록했습니다. 이 수정은 초기 읽기가 실패하면 동기화 및 정리 작업을 중단하고, 쓰기 후 검증 읽기가 실패하면 실패를 반환하도록 합니다.

손상된 hosts 파일을 복원하려면 권한을 사용하여 파일을 편집하세요:

sudo nano /etc/hosts

최소한 표준 루프백 항목은 복원하세요. 예를 들면:

127.0.0.1	localhost
127.0.0.1	myhost
::1		localhost ip6-localhost ip6-loopback
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters

백업이 있으면 대신 백업을 복원하세요:

sudo cp /etc/hosts.bak /etc/hosts

임시 해결 방법으로, /etc/hosts가 잠겨 있거나 사용할 수 없거나 읽을 수 없는 상태일 수 있는 상황에서는 영향을 받는 Portless 버전을 실행하지 말고, 프로세스에 해당 파일을 읽고 쓰는 데 필요한 권한이 있는지 확인하세요.

원본 Issue #419
문제 해결
왜 Portless는 macOS에서 이미 사용 중인 포트를 할당하고 readiness 검사를 잘못된 프로세스로 라우팅하나요?

이 문제는 Portless 0.12.0이 127.0.0.1:<port>로 프록시하면서 호스트를 지정하지 않은 net.createServer().listen(port) 바인드로 빈 포트를 탐지하기 때문에 발생합니다. macOS에서는 다른 프로세스가 이미 IPv4 루프백에서 리스닝 중이어도 탐지가 성공할 수 있습니다.

즉시 적용 가능한 해결 방법:
1. 남아 있는 리스너를 찾습니다:
lsof -nP -iTCP:<port> -sTCP:LISTEN
2. kill <PID>로 해당 프로세스를 중지하거나, PORT=<free-port>로 비어 있음이 확인된 포트를 명시적으로 지정합니다.
3. 재시작하기 전에 두 루프백 스택 모두에서 포트가 비어 있는지 확인합니다:
lsof -nP -iTCP@127.0.0.1:<port> -sTCP:LISTEN
lsof -nP -iTCP@[::1]:<port> -sTCP:LISTEN

영구적인 수정: issue #288에 대한 수정이 포함된 Portless 릴리스로 업그레이드하세요 (PR #441, #302 기반). 수정된 포트 탐지는 호스트를 지정하지 않은 바인드를 사용하는 대신 127.0.0.1, ::1, 0.0.0.0을 포함한 관련 로컬 주소에서 후보 포트를 확인합니다.

원본 Issue #288
문제 해결
portless가 asdf, nvm, fnm 또는 mise의 Node 버전 대신 Homebrew Node 25를 사용하는 문제를 어떻게 해결하나요?

이 문제는 영향을 받는 portless 버전이 자식 프로세스의 PATH 앞에 path.dirname(process.execPath)를 추가하기 때문에 발생합니다. 이로 인해 portless를 실행하는 Node가 버전 관리자에서 선택한 버전을 가리게 됩니다.

portless를 issue #241 수정이 포함된 릴리스로 업그레이드하세요. 이 수정은 사용자의 기존 PATH를 portless 자체 Node 디렉터리보다 앞에 배치하므로(또는 Windows에서는 필요한 경우에만 해당 디렉터리를 주입하므로) 버전 관리자 shim이 먼저 해석됩니다.

brew upgrade portless
# 또는 관리 중인 Node 아래에 npm으로 설치한 경우:
npm update -g portless

portless myapp sh -c 'node --version'

업그레이드할 수 없는 경우의 임시 해결 방법: Homebrew로 설치된 portless를 제거하고 asdf/nvm/fnm/mise가 관리하는 Node로 설치하세요:

brew uninstall portless
nvm use 24          # 또는: fnm use 24
# asdf: asdf shell nodejs 24.13.0
# mise: mise use -g node@24
npm install -g portless
portless myapp sh -c 'node --version'

원본 Issue #241