Portless로 localhost:3000 대신 이름 있는 URL 쓰기
로컬에서 개발하다 보면 http://localhost:3000, http://localhost:3001, http://localhost:8080 같은 주소를 하루에도 수십 번씩 입력하는데요. 프로젝트가 두세 개만 돼도 어느 포트가 어느 앱이었는지 헷갈리기 시작합니다. 프런트엔드는 3000번, API는 4000번, 스토리북은 6006번… 이걸 외우고 있다가 동료에게 “그거 몇 번 포트였죠?”라고 물어본 경험, 다들 한 번쯤 있으시죠. 😅
게다가 포트가 겹치면 EADDRINUSE 에러가 나면서 서버가 안 뜨고, HTTPS가 필요한 기능(쿠키의 Secure 속성이나 일부 브라우저 API)을 테스트하려면 인증서를 직접 만들어 신뢰 저장소에 등록하는 번거로운 과정을 거쳐야 합니다.
이런 불편함을 한 방에 해결해 주는 도구가 바로 Vercel에서 만든 Portless입니다. 포트 번호 대신 https://myapp.localhost 같은 이름 있는 URL을 쓸 수 있게 해주는데요. 이번 글에서는 Portless가 무엇이고 어떻게 동작하는지, 그리고 실제로 어떻게 활용하는지 차근차근 살펴보겠습니다.
Portless란?
Portless는 로컬 개발 환경에서 동작하는 작은 프록시(proxy) 도구입니다. 핵심 아이디어는 단순합니다. 포트 번호를 사람이 외우기 쉬운 이름으로 바꿔주는 것이죠.
기존에는 개발 서버를 띄우면 http://localhost:3000처럼 포트 번호로 접속했습니다. Portless를 거치면 같은 서버를 https://myapp.localhost라는 이름 있는 주소로 접속할 수 있습니다. 이름은 우리가 직접 정하니까 외울 필요도 없고, 포트가 3000이든 3001이든 신경 쓸 필요가 없습니다.
동작 방식은 이렇습니다. Portless 프록시가 백그라운드에서 떠 있다가, 우리가 앱을 실행하면 비어 있는 포트(4000~4999 범위)를 하나 골라 앱에 할당하고 그 포트와 이름을 연결해 둡니다. 그다음 브라우저에서 https://myapp.localhost로 요청이 들어오면 프록시가 실제 포트로 트래픽을 넘겨주는 식입니다. 우리는 더 이상 포트 번호를 볼 일이 없어지는 거죠.
이름만 보면 localhost의 서브도메인인데, 어떻게 별도 설정 없이 동작하는지 궁금하실 텐데요. localhost와 127.0.0.1의 관계가 궁금하다면 localhost와 127.0.0.1의 차이를 함께 읽어보시면 이해가 더 쉽습니다. 대부분의 최신 브라우저(Chrome, Firefox, Edge)는 *.localhost 형태의 모든 서브도메인을 자동으로 127.0.0.1로 해석하기 때문에, 별도의 hosts 파일 수정 없이도 곧바로 동작합니다.
설치하기
Portless는 npm 패키지로 배포됩니다. 여러 프로젝트에서 두루 쓰는 도구이니 전역으로 설치하는 것을 권장합니다.
npm install -g portless
특정 프로젝트에서만 쓰고 싶다면 개발 의존성으로 설치할 수도 있습니다.
npm install -D portless
다만 Portless는 아직 1.0 이전 버전이라, 프로젝트별로 설치하면 팀원마다 다른 버전을 쓰게 될 수 있다는 점은 알아두세요. 그리고 Node.js 24 버전 이상이 필요합니다.
기본 사용법
가장 기본적인 사용법은 평소 개발 서버를 실행하던 명령어 앞에 portless와 이름을 붙이는 것입니다.
portless myapp next dev
이렇게 실행하면 Next.js 개발 서버가 뜨면서 https://myapp.localhost라는 주소가 만들어집니다. 여기서 myapp이 우리가 정한 이름이고, next dev가 원래 실행하려던 명령어입니다.
이름을 매번 적기 귀찮다면 프로젝트 정보를 보고 자동으로 이름을 추론하게 할 수도 있습니다.
portless run next dev
이 경우 package.json이나 디렉터리 이름에서 앱 이름을 알아서 가져옵니다. 현재 어떤 라우트가 등록돼 있는지 확인하고 싶을 때는 list 명령어를 사용합니다.
portless list
프록시는 앱을 실행할 때 자동으로 시작되지만, 명시적으로 먼저 띄워두고 싶다면 직접 시작할 수도 있습니다.
portless proxy start
HTTPS가 기본으로 켜진다
Portless의 가장 반가운 점 중 하나는 HTTPS가 기본으로 활성화된다는 것입니다. 게다가 HTTP/2까지 함께 적용되죠.
보통 로컬에서 HTTPS를 쓰려면 mkcert 같은 도구로 인증서를 만들고 이를 운영체제 신뢰 저장소에 등록하는 과정이 필요한데요. Portless는 처음 실행될 때 로컬 인증 기관(CA)을 자동으로 생성하고 시스템 신뢰 저장소에 등록해 줍니다. 그래서 브라우저에서 “안전하지 않은 연결”이라는 경고 없이 곧바로 자물쇠 아이콘이 붙은 주소를 쓸 수 있습니다. HTTPS가 어떻게 신뢰를 만들어내는지 그 원리가 궁금하다면 HTTPS와 TLS 인증서 이야기를 참고해 보세요.
혹시 나중에 인증서 신뢰 등록만 따로 다시 하고 싶다면 다음 명령어를 쓸 수 있습니다.
portless trust
반대로 HTTPS가 필요 없는 상황이라면 평범한 HTTP로 띄울 수도 있습니다.
portless proxy start --no-tls
서브도메인과 모노레포
앱이 하나가 아니라 여러 개라면 어떻게 할까요? 프런트엔드, API, 문서 사이트를 동시에 띄우는 경우가 흔하죠. Portless에서는 점(.)을 이용한 서브도메인으로 깔끔하게 구분할 수 있습니다.
portless api.myapp pnpm start
# → https://api.myapp.localhost
portless docs.myapp next dev
# → https://docs.myapp.localhost
이렇게 하면 myapp.localhost, api.myapp.localhost, docs.myapp.localhost처럼 한눈에 어떤 서비스인지 알 수 있는 주소 체계가 만들어집니다.
pnpm이나 yarn 워크스페이스로 구성된 모노레포라면 더 편리합니다. Portless가 워크스페이스 설정을 읽어서 패키지들을 자동으로 찾아주거든요. 저장소 루트에서 portless를 실행하면 dev 스크립트를 가진 모든 워크스페이스 패키지가 한꺼번에 뜨고, 각 패키지는 <패키지>.<프로젝트>.localhost 규칙에 따라 주소를 부여받습니다.
이름이나 스크립트를 직접 지정하고 싶을 때는 저장소 루트에 portless.json 파일을 두면 됩니다.
{
"name": "myapp",
"apps": {
"apps/web": { "name": "web" },
"apps/api": { "name": "api" }
}
}
package.json에 한 줄 추가하는 방식도 가능합니다.
{
"name": "@myorg/web",
"portless": "myapp"
}
Git worktree마다 다른 주소
Git worktree를 즐겨 쓴다면 정말 마음에 드실 기능이 하나 있습니다. Portless는 worktree를 자동으로 감지해서, 연결된 worktree에는 브랜치 이름을 서브도메인으로 붙여줍니다.
예를 들어 메인 체크아웃에서는 평범한 주소가 만들어집니다.
portless run next dev
# → https://myapp.localhost
그런데 fix-ui라는 브랜치의 worktree에서 똑같은 명령어를 실행하면 브랜치 이름이 앞에 붙습니다.
portless run next dev
# → https://fix-ui.myapp.localhost
덕분에 여러 브랜치를 동시에 띄워놓고 비교하더라도 주소가 겹치지 않습니다. package.json에 portless run을 한 번만 설정해 두면 모든 worktree에서 알아서 동작하니 별도 설정도 필요 없습니다.
Docker 컨테이너에 이름 붙이기
Portless로 직접 실행하지 않는 서비스, 예를 들어 Docker 컨테이너로 띄운 서버에도 이름 있는 주소를 붙일 수 있습니다. alias 명령어로 이름과 포트를 직접 연결하면 됩니다.
portless alias myapp 3000
이렇게 하면 3000번 포트에서 돌고 있는 서비스가 https://myapp.localhost로 접속 가능해집니다. 컨테이너가 어떤 도구로 떠 있든 상관없이, 이미 떠 있는 포트를 가리키는 정적 라우트를 등록하는 방식이죠.
팀과 공유하기
만든 결과물을 동료에게 보여줘야 할 때도 있죠. Portless는 Tailscale과 ngrok 연동을 기본으로 제공합니다.
같은 Tailscale 네트워크(테일넷)에 속한 동료에게 공유하려면 --tailscale 플래그를 붙입니다.
portless myapp --tailscale next dev
# 로컬: https://myapp.localhost
# 테일넷: https://devbox.yourteam.ts.net
WireGuard 기반 메시 VPN인 Tailscale의 자세한 활용법이 궁금하다면 Tailscale로 어디서든 내 기기에 접속하기에서 더 깊이 다루고 있습니다.
공개 인터넷에 잠깐 노출해야 한다면 ngrok을 쓸 수 있습니다. 외부 서비스의 웹훅(Webhook)을 테스트할 때 특히 유용하죠.
portless myapp --ngrok next dev
# 로컬: https://myapp.localhost
# 공개: https://abc123.ngrok.app
두 방식 모두 앱을 종료하면 공유 설정이 자동으로 정리됩니다. 다만 Tailscale은 tailscale up으로 연결돼 있어야 하고, ngrok은 인증 토큰이 등록돼 있어야 합니다. 터널링 도구로 로컬 서버를 외부에 공개하는 방법이 처음이라면 ngrok으로 로컬 서버를 인터넷에 공개하기를 먼저 읽어보시길 권합니다.
알아두면 좋은 점들
마지막으로 실제로 써보기 전에 챙겨두면 좋은 몇 가지를 정리해 보겠습니다.
우선 Safari는 시스템 DNS 해석기에 의존하기 때문에 .localhost 서브도메인이 환경에 따라 자동으로 해석되지 않을 수 있습니다. 이럴 때는 /etc/hosts에 라우트를 직접 동기화해 주면 됩니다.
portless hosts sync # 현재 라우트를 hosts에 추가
portless hosts clean # 나중에 정리
또한 .localhost 대신 다른 최상위 도메인을 쓰고 싶다면 --tld 옵션을 활용할 수 있습니다. 공식 문서에서는 IANA가 예약해 둔 .test를 권장합니다. .local은 mDNS와 충돌하고 .dev는 강제 HTTPS 때문에 피하는 게 좋습니다.
portless proxy start --tld test
# → https://myapp.test
그리고 Portless로 띄운 두 앱이 서로 요청을 주고받을 때는 프록시가 Host 헤더를 다시 쓰도록 설정해야 합니다. 그렇지 않으면 요청이 무한 루프에 빠질 수 있는데, Portless가 이를 감지해서 508 Loop Detected 응답과 함께 해결 방법을 안내해 줍니다.
다 쓰고 나서 흔적을 깨끗하게 지우고 싶다면 clean 명령어 하나로 상태 디렉터리, 등록한 CA, /etc/hosts 항목을 한꺼번에 제거할 수 있습니다.
portless clean
마치며
지금까지 Vercel의 Portless로 포트 번호 대신 이름 있는 HTTPS URL을 쓰는 방법을 살펴봤습니다. 핵심만 다시 정리하면, 로컬 프록시가 포트를 자동으로 할당하고 https://myapp.localhost 같은 주소로 라우팅해 주며, HTTPS 인증서 등록부터 모노레포 자동 탐색, Git worktree별 서브도메인, 팀 공유까지 한 도구에서 해결됩니다.
매일 포트 번호를 외우고 충돌을 피하느라 신경 쓰던 사소한 피로가 생각보다 크다는 걸, 막상 이름 있는 주소를 쓰기 시작하면 실감하게 됩니다. 아직 1.0 이전 버전이라 변화의 여지는 있지만, 로컬 개발 경험을 한 단계 끌어올리고 싶다면 한번 도입해 볼 만한 도구입니다.
더 자세한 명령어와 옵션은 Portless 공식 저장소를 참고하세요.
This work is licensed under
CC BY 4.0