발단 : 로컬에 셋팅한 PaperClip을 스마트폰으로도 접속해서 오더를 내리고 관리하고 싶다.

PaperClip은 최근 공개된 오픈소스 AI 에이전트 오케스트레이션 플랫폼이다. 여러 AI 에이전트(Claude Code, Cursor 등)를 마치 회사의 팀원처럼 조직화하고 관리할 수 있게 해주는 도구로, 목표 정렬, 비용 관리, 거버넌스, 스케줄링 등의 기능을 제공한다. npx paperclipai onboard --yes 한 줄이면 로컬에 설치되고, 임베디드 PostgreSQL과 함께 localhost:3100에서 바로 실행된다.

이렇게 로컬에 셋팅해둔 PaperClip을 스마트폰으로 접속할 수 있게 외부 도메인(abc.mydomain.com)을 붙이고 싶었다.

이 작업은 처음부터 끝까지 Claude Code(Anthropic의 CLI 에이전트)와 대화하면서 진행했다. 내가 "도메인으로 외부에서 접속할 수 있게 해줘"라고 요청하자, Claude Code가 현재 환경을 분석하고, 선택지를 제시하고, 설정 파일을 작성하고, 서비스를 등록하는 것까지 전부 처리해줬다. 그 과정을 시간순으로 기록한다.

(PaperClip 자체에 대한 소개와 탐구는 다음 글에서 자세히 다룰 예정이다.)


1단계: 어떤 방법으로 외부 접속을 열 것인가?

Claude Code가 가장 먼저 물어본 것은 "어떤 방식으로 외부 접속을 구성할 것인가"였다. 네 가지 선택지를 제시해줬다:

방법 장점 단점
Cloudflare Tunnel 무료, 포트 개방 불필요, SSL 자동 Cloudflare 계정 필요, NS 이전 필요
Nginx 리버스 프록시 전통적이고 검증된 방식 고정 IP 필요, SSL 별도 설정
ngrok 설치 즉시 사용 가능 무료는 랜덤 URL, 커스텀 도메인은 유료
Tailscale Funnel Tailscale 네트워크 활용 Tailscale 계정 필요, 커스텀 도메인 제한

나는 이미 도메인이 있었고, Nginx가 익숙하기도 해서 Nginx 리버스 프록시를 선택했다. SSL은 Let's Encrypt로 처리하면 될 것 같았다.


2단계: 동적 IP 문제 해결

그런데 맥북은 노트북이다. 카페에서, 집에서, 사무실에서 — 접속할 때마다 IP가 바뀐다. Nginx 리버스 프록시 방식은 도메인의 A 레코드에 서버 IP를 고정해야 하는데, IP가 계속 바뀌면 그때마다 DNS를 수정해줘야 한다. 현실적으로 불가능하다.

이 문제를 Claude Code에 공유하자, 다시 선택지를 정리해줬다:

  1. ngrok — 무료 플랜에서 고정 도메인 1개 제공. 기존 DNS에서 CNAME으로 연결 가능. 하지만 커스텀 도메인은 유료.
  2. Cloudflare Tunnel + 기존 DNS 유지 — Cloudflare Free 플랜에서는 서브도메인 위임(partial setup)을 지원하지 않아 실질적으로 불가.
  3. Cloudflare로 NS 이전 후 Tunnel 사용 — 기존 DNS 레코드를 Cloudflare가 자동 스캔해서 복제. 관리 화면만 바뀔 뿐 기능은 동일. 가장 안정적이고 무료.

Cloudflare Tunnel의 핵심 원리는 이렇다: 맥북에서 Cloudflare 엣지 서버로 아웃바운드 연결을 맺는다. 외부에서 들어오는 요청은 Cloudflare가 이 터널을 통해 맥북으로 전달한다. 즉, 맥북의 IP가 뭐든 상관없다. 포트 포워딩도 필요 없고, 방화벽도 신경 쓸 필요 없다.


3단계: DNS를 Cloudflare로 옮겨야 하는가?

사실 이 부분이 가장 고민됐다. 나는 기존에 dnszi라는 무료 DNS 관리 서비스를 사용하고 있었다. 여기에 A 레코드, TXT(SPF) 레코드 등이 설정되어 있었는데, 이걸 통째로 Cloudflare로 옮겨야 한다는 거다.

Claude Code가 먼저 현재 DNS 상태를 dig 명령으로 조회해서 백업해줬다:

dig mydomain.com ANY +noall +answer
레코드 타입
NS dnszi.com 네임서버 5개
A 기존 서버 IP
TXT (SPF) SPF 레코드
MX 없음

백업을 확인한 뒤, Cloudflare로 이전하기로 결정했다. 어차피 Cloudflare가 기존 레코드를 자동 스캔해서 가져와 주고, 이후에도 같은 방식으로 관리할 수 있으니까.


4단계: Cloudflare 도메인 등록 및 dnszi에서 이전

Cloudflare 계정이 없어서 새로 만들었다. 이후 과정은 다음과 같다:

  1. dash.cloudflare.com에서 "Add a site" → 도메인 입력
  2. Free 플랜 선택
  3. Cloudflare가 기존 DNS 레코드를 자동 스캔 — dnszi에 설정했던 A 레코드, TXT 레코드 등이 그대로 복제됨
  4. Cloudflare에서 제공하는 네임서버 2개를 확인
  5. 도메인 등록기관(도메인을 구매한 곳)에서 네임서버를 dnszi의 것에서 Cloudflare 것으로 변경

네임서버 전파에는 보통 10분~1시간이 걸리고, 최대 48시간까지 소요될 수 있다. 전파되는 동안 터널 설정을 먼저 진행했다.


5단계: Cloudflare Tunnel 설정 — Claude Code가 전부 처리

여기서부터는 Claude Code가 명령어를 직접 실행하면서 진행했다. 내가 한 건 cloudflared tunnel login 실행 후 브라우저에서 도메인 인증을 클릭한 것뿐이다.

5-1. cloudflared 설치

brew install cloudflared

5-2. Cloudflare 계정 인증

cloudflared tunnel login
# 브라우저가 열리고, 도메인을 선택(authorize)하면 완료
# 인증서가 ~/.cloudflared/cert.pem에 저장됨

5-3. 터널 생성

cloudflared tunnel create paperclip
# Tunnel credentials written to ~/.cloudflared/<터널ID>.json
# Created tunnel paperclip with id <터널ID>

5-4. DNS 라우팅 연결

cloudflared tunnel route dns paperclip abc.mydomain.com
# Cloudflare DNS에 CNAME 레코드가 자동 추가됨

5-5. 설정 파일 작성

Claude Code가 ~/.cloudflared/config.yml을 자동으로 생성해줬다:

tunnel: <터널ID>
credentials-file: /Users/<username>/.cloudflared/<터널ID>.json

ingress:
  - hostname: abc.mydomain.com
    service: http://127.0.0.1:3100
  - service: http_status:404

5-6. 터널 실행 및 확인

cloudflared tunnel run paperclip

서울(ICN) Cloudflare 엣지에 4개 커넥션이 연결되었다. NS 전파가 끝나면 바로 접속 가능해진다.


6단계: 맥북 재부팅에도 자동 실행되도록 설정

터널이 잘 작동하는 걸 확인한 후, 재부팅해도 자동으로 뜨도록 macOS LaunchAgent에 등록했다. 여기서도 Claude Code가 처리해줬는데, 두 가지 서비스를 모두 등록해야 했다.

6-1. Cloudflare Tunnel 자동 시작

cloudflared service install

이 명령으로 LaunchAgent가 등록되지만, 한 가지 함정이 있었다. 자동 생성된 plist 파일의 ProgramArgumentstunnel run paperclip 인자가 빠져 있어서, 서비스가 시작은 되지만 터널이 열리지 않았다. Claude Code가 로그를 확인하고 바로 plist를 수정해줬다:

<!-- ~/Library/LaunchAgents/com.cloudflare.cloudflared.plist -->
<array>
    <string>/opt/homebrew/bin/cloudflared</string>
    <string>tunnel</string>
    <string>run</string>
    <string>paperclip</string>
</array>

6-2. PaperClip 서비스 자동 시작

PaperClip 자체도 자동 시작이 등록되어 있지 않았다. Claude Code가 ps로 현재 실행 방식을 확인한 뒤, LaunchAgent plist를 새로 작성해줬다:

<!-- ~/Library/LaunchAgents/com.paperclip.server.plist -->
<key>ProgramArguments</key>
<array>
    <string>/path/to/npx</string>
    <string>paperclipai</string>
    <string>onboard</string>
    <string>--yes</string>
</array>

등록 후 launchctl load로 서비스를 시작하고, curl http://127.0.0.1:3100으로 HTTP 200 응답을 확인했다.


7단계: 트러블슈팅 — 포트가 두 개?

서비스 등록 직후 이상한 걸 발견했다. localhost:3100localhost:3101 둘 다 PaperClip이 열려 있었다. Claude Code가 lsof로 원인을 바로 찾아줬다:

포트 원인
3100 아까 수동으로 실행했던 원래 프로세스
3101 LaunchAgent가 띄운 새 인스턴스 (3100이 점유되어 자동으로 다음 포트 사용)

해결은 간단했다. 수동 프로세스를 종료하고 서비스를 재시작하면 새 인스턴스가 3100 포트를 잡는다:

kill <수동 실행 PID>
launchctl unload ~/Library/LaunchAgents/com.paperclip.server.plist
launchctl load ~/Library/LaunchAgents/com.paperclip.server.plist

최종 결과

항목 설정
PaperClip localhost:3100, LaunchAgent으로 자동 시작 + 크래시 시 자동 재시작
Cloudflare Tunnel abc.mydomain.com → localhost:3100, LaunchAgent으로 자동 시작
SSL Cloudflare 자동 제공 (별도 인증서 설정 불필요)
동적 IP 아웃바운드 터널이므로 IP 변경과 무관
DNS dnszi → Cloudflare로 자동 이전 완료

맥북을 재부팅하더라도 PaperClip과 Cloudflare Tunnel이 자동으로 시작되며, 어디서든 https://abc.mydomain.com으로 접속할 수 있다.


회고: Claude Code와 함께한 인프라 작업

솔직히 이 작업을 혼자 했다면 한참 걸렸을 것이다. ngrok으로 로컬에 띄운 웹서비스를 외부도메인에 연결할수 있다는 정도의 사전지식만 있었을 뿐 Cloudflare Tunnel이라는 선택지 자체도 몰랐다.

Claude Code가 좋았던 점은 단순히 명령어를 대신 쳐준 것이 아니라, 각 단계에서 선택지를 제시하고 장단점을 설명해준 것이다. "Nginx로 할까, Cloudflare Tunnel로 할까", "기존 DNS를 유지할까, 이전할까" — 이런 의사결정 포인트에서 판단 근거를 함께 제공해줬기 때문에 빠르게 결정할 수 있었다.

또한 plist에 인자가 빠진 버그나, 포트 중복 문제 같은 트러블슈팅도 로그를 확인하고 즉시 원인을 파악해서 고쳐줬다. 전체 과정이 하나의 대화 세션에서 약 20분 만에 끝났다.

로컬 서비스를 도메인으로 열어야 하는 비슷한 상황이라면, Cloudflare Tunnel을 추천한다. 특히 고정 IP가 없는 환경에서는 사실상 유일한 무료 해결책이다. (단, 당연하게도  맥북이 켜져있고 인터넷에 연결되어 있어야만 가능)

+ Recent posts