로컬 개발 서버의 주소는 대개 포트 번호로 결정됩니다. Next.js는 3000, Vite는 5173처럼 익숙한 숫자가 있지만, 여러 앱과 브랜치를 동시에 띄우면 이 규칙은 금방 흐트러집니다. vercel-labs/portless는 `portless run next dev`를 통해 앱을 `https://myapp.localhost` 같은 안정적인 이름 기반 URL로 노출합니다.
작동 방식은 프록시를 중심으로 돌아갑니다. portless는 앱을 시작할 때 프록시를 자동 실행하고, 자식 프로세스에 4000~4999 사이의 포트를 `PORT` 환경변수로 전달합니다. Next.js, Express, Nuxt처럼 이 변수를 읽는 프레임워크는 별도 수정 없이 연결됩니다. Vite, Astro, Angular처럼 `PORT`를 무시하는 도구에는 실행 명령을 분석해 `--port`를 주입하고, 필요하면 `--host`도 붙입니다. `vite build`나 `astro check`처럼 서버를 띄우지 않는 명령에는 플래그를 넣지 않습니다.
여기서 portless의 성격이 드러납니다. 특정 프레임워크용 플러그인을 하나씩 제공하기보다, 어떤 명령이 서버를 시작하는지 판단하고 그 앞뒤를 관리합니다. 프록시는 최근 실행에 사용한 포트, TLS, TLD 설정을 재사용합니다. 재시작이나 재부팅 뒤 기본값으로 조용히 돌아가지 않으며, `PORTLESS_PORT`, `PORTLESS_HTTPS` 같은 명시적 환경변수가 있으면 그것을 우선합니다.
HTTPS와 HTTP/2가 기본이고, 첫 실행 때 로컬 CA를 생성해 신뢰 설정을 합니다. macOS와 Linux에서는 443 포트 바인딩을 위해 sudo 승격이 필요할 수 있습니다. 평문 HTTP가 필요한 환경에서는 `--no-tls`를 사용할 수 있습니다. 기본 TLD인 `.localhost` 대신 `.test`를 선택하면 portless가 라우트 호스트를 `/etc/hosts`에 동기화합니다.
Git worktree 지원은 이름 기반 주소의 장점을 실제 협업 흐름에 연결합니다. 기본 작업 공간이 `myapp.localhost`라면 `fix-ui` 브랜치의 linked worktree는 `fix-ui.myapp.localhost`를 사용합니다. 같은 프로젝트를 여러 작업 공간에서 실행할 때 포트 충돌을 피하기 위해 설정 파일을 복사할 필요가 없습니다.
모노레포에서는 루트 `portless.json`으로 workspace 패키지를 관리합니다. pnpm workspace나 package.json의 workspaces를 찾아 패키지별 주소를 만들고, 필요하면 `apps` 맵에서 이름을 덮어쓸 수 있습니다. Turbo와 함께 쓸 때는 `dev` script에 portless를 두고 실제 앱 명령을 `dev:app`으로 분리합니다. Turbo 설정을 새로 고칠 필요 없이 각 패키지가 자신의 이름 있는 URL을 얻습니다.
자동화의 범위에는 선이 있습니다. `&&`, 파이프, 세미콜론이 있는 복합 명령이나 환경변수 접두어, 다른 script 위임처럼 플래그 주입이 안전하지 않은 경우 portless는 명령을 그대로 둡니다. CI 또는 TTY가 없는 환경에서도 프롬프트를 띄우지 않고 설명적인 오류로 종료합니다. 프로젝트별 dev dependency 설치는 팀원마다 버전이 달라질 수 있고, 상태 디렉터리 형식 변경 뒤에는 `portless trust`를 다시 실행해야 할 수도 있습니다.
그래서 이 저장소를 볼 때 눈에 먼저 들어오는 `.localhost`보다 중요한 것은 실행의 재구성입니다. portless는 앱을 포트 번호와 단일 프로세스의 조합으로 보지 않고, 이름, 라우트, TLS, worktree, 종료 처리를 묶은 하나의 개발 실행 단위로 다룹니다. 플러그인을 추가하는 접근보다, 로컬 서버를 시작하고 찾고 멈추는 흐름 전체를 다시 설계한 접근에 가깝습니다.