Jieun 8 분 분량 수정
Systems

Part 1 — 16GB 맥북 네 대 대신 96GB VPS를 선택한 이유

코드는 서버에 있고 컴파일도 서버가 하고, 맥북은 화면만 받습니다. 네 명이 한 대를 나눠 쓰게 되면서 이 구조가 어디까지 맞고 어디부터 타협인지 다시 따져봤습니다. dev 서버 하나가 4시간에 1.2GB에서 3.7GB로 자라는 걸 보고서야 무엇을 서버에 둬야 하는지 분명해졌습니다.

맥북이 SSH로 개발 서버에 접속하고, 현재 :3100에서 :8787 터널을 거쳐 운영 API·DB로 가는 실선 경로와 추후 서버 내부의 Playwright·:39xx·로컬 API·DB·Redis로 분리할 점선 경로
현재 화면 확인 경로는 운영 API를 보지만, e2e는 추후 별도 로컬 스택으로 분리합니다.
목차

들어가며

각자 16GB RAM 맥북으로 개발을 시작했는데, 코드와 개발 도구, 에이전트를 함께 돌리다 보니 한계가 금방 보였습니다. 빌드가 무거워질수록 메모리가 모자라고, 여러 프로젝트와 코딩 에이전트를 동시에 띄우면 팬이 돌면서 다른 작업까지 느려졌습니다.

그래서 각자 맥북의 사양을 계속 올리는 대신, 비용이 낮은 리눅스 서버 한 대를 빌렸습니다. 코드는 서버에 두고, 코딩과 컴파일, 코딩 에이전트 실행도 그 안에서 하기로 했습니다. 그 결과 지금은 96GB / 18코어 리눅스 한 대를 네 명이 나눠 씁니다.

사람이 늘면서 구조를 다시 봐야 했습니다. 각자 맥북에서 SSH로 붙고, dev 서버는 그 리눅스에서 돌고, 데이터는 운영을 봅니다. 3단입니다.

어디까지가 좋은 설계이고 어디부터 타협인지, 그리고 e2e 테스트를 왜 이 위에 올리면 안 되는지 정리했습니다.


1. 지금 구조는 이렇게 생겼습니다

고른 건 Contabo VPS였습니다. 중요한 건 최고 사양이 아니라 16GB 맥북 네 대를 업그레이드하는 비용보다 낮은 비용으로, 여러 사람이 함께 쓸 수 있는 메모리와 코어를 확보하는 것이었습니다. 한국에서 접속하는 만큼 리전은 도쿄로 골랐습니다. SSH 연결뿐 아니라 브라우저로 화면을 확인할 때의 HMR 왕복도 계속 발생하므로, 서버와 사용자의 거리가 짧아야 체감 레이턴시가 낮아집니다.

맥북 브라우저만. 개발 프로세스 0개, 코드도 없음
│ ssh -N -L 3100:127.0.0.1:3100 서버
▼
서버 next dev :3100 ← 프론트 + BFF 둘 다
│ API_BASE=http://127.0.0.1:8787 18코어 / 96GB / 코딩 에이전트
│ wdot-api-tunnel (systemd) — ssh -N -L 8787:127.0.0.1:8987
▼
운영 서버 api :8987 → 운영 DB ★ 운영입니다

화면 서버도 API 연결이 필요합니다

여기서 next dev는 정적 화면만 띄우는 프로세스가 아닙니다. admin은 BFF(Backend For Frontend) 역할을 합니다. 브라우저가 보는 화면을 위해 Next.js 서버가 먼저 API를 호출하고, 그 결과를 바탕으로 화면을 렌더링합니다. API와 DB를 직접 소유하는 백엔드는 따로 있고, admin은 그 앞에서 화면에 필요한 형태로 데이터를 가져옵니다.

이렇게 나눈 이유는 자격증명 경계 때문입니다. admin에는 DATABASE_URL을 두지 않았고, DB 자격증명은 API와 worker 쪽에만 있습니다. Next.js도 서버 컴포넌트와 클라이언트 컴포넌트의 경계를 나누며, 토큰 같은 서버 전용 데이터는 서버 쪽에 두고 클라이언트에는 직렬화 가능한 props를 넘기는 흐름을 설명합니다. 화면 서버가 뚫리더라도 그 자리에서 DB 접속 정보를 얻을 수 없게 하려는 선택이었습니다. API 데이터를 사용하는 화면은 렌더링 시점에 해당 API에 연결할 수 있어야 합니다. 또한 클라이언트 컴포넌트에 전달한 데이터는 브라우저로 직렬화되므로, 서버에서 가져온 응답이 모두 비공개로 남는다는 뜻은 아닙니다.

그래서 로컬이나 원격 개발 서버에 admin만 띄워도 API 연결이 없으면 화면이 제대로 만들어지지 않습니다. 목록·통계·카드처럼 실제 데이터가 필요한 화면은 빈 껍데기가 되거나 500으로 끝납니다. 별도 로컬 DB와 충분한 시드 데이터가 있었다면 운영 API를 보지 않아도 됐겠지만, 당시에는 그 준비가 없었습니다.

그 결과 현재 구조는 2분할이 아니라 “브라우저 / 개발 프로세스 / 운영 데이터” 3단입니다. 브라우저는 맥북에 남기고 개발 프로세스를 VPS로 옮긴 결정은 작업 환경 문제를 풀었지만, 셋째 단이 운영 API라는 사실은 별도 판단이 필요한 타협으로 남았습니다.


2. 앞 2단이 맞았던 이유 세 개

기본이 닫혀 있습니다

dev 포트가 전부 루프백에만 붙습니다.

127.0.0.1:3000
127.0.0.1:3100
127.0.0.1:8787 ← API 터널

외부 NIC에 붙은 dev 포트가 하나도 없습니다. SSH 말고는 들어올 길이 없습니다.

여러 명이 쓰는 장비에서 이게 큰 값입니다. 포트를 잘못 주거나 바인드 주소를 빼먹어도 노출되지 않습니다. 실수의 결과가 “안 열림”이지 “다 열림”이 아닙니다.

보는 통로와 돌리는 통로가 따로입니다

dev 서버를 nohup으로 띄우기 때문에 SSH가 끊겨도 서버가 삽니다. 터널은 창문일 뿐이고 실행 경로가 아닙니다.

맥북 뚜껑을 닫아도 컴파일 상태가 남아 있고, 다시 붙으면 그대로입니다. 카페에서 회선이 끊겨도 빌드가 날아가지 않습니다.

원격 화면(VNC·RDP)은 연결이 곧 세션이라 끊기면 거기서 끝입니다. 이 구조는 연결이 끊겨도 일이 계속 돕니다. 쓰다 보면 이 차이가 제일 크게 느껴집니다. 회선을 신경 쓰지 않게 됩니다.

셋째 단으로 가는 API 터널도 같은 생각을 한 층 아래에 적용한 것입니다. 사람의 SSH 세션이 아니라 nologin 전용 계정으로 도는 systemd 서비스가 그 터널을 뭅니다. 그래서 아무도 접속하지 않은 새벽에도, 재부팅 뒤에도 터널이 서 있습니다. 처음엔 이게 한 사람의 노트북이 밀어주는 구조였는데, 그 이야기는 2부에 있습니다.

무거운 쪽이 힘센 쪽에 있습니다

dev 서버 하나의 메모리를 시간 간격으로 재봤습니다.

05:28 1,259MB
08:1x 2,015MB
09:23 2,741MB
10:00 3,759MB ← 4시간 동안 3배

Next.js dev 서버는 켜 둘수록 자랍니다. 이게 맥북에서 돌면 팬이 계속 돌고 다른 앱까지 느려집니다. 18코어/96GB가 대신 합니다.

그리고 코드·에이전트·컴파일이 한 곳에 모여 있어서 동기화 지연이 없습니다. 파일 동기화 도구를 끼우는 방식과 결정적으로 다른 지점입니다. 코딩 에이전트가 고친 파일을 dev 서버가 그 자리에서 봅니다.

환경도 한 벌이라 “내 맥에선 되는데”가 구조적으로 안 생깁니다.


3. 대가도 있습니다

깔끔한 계층 분리는 아닙니다. 구멍이 하나 있습니다.

구글 OAuth 콜백이 http://localhost:3100/...으로 등록돼 있습니다. 노트북 쪽 포워딩 번호를 바꾸면 화면은 떠도 로그인이 redirect_uri_mismatch로 끝납니다.

맥북 :3100 ──ssh── 서버 :3100
▲ ▲
└── 이 둘이 같아야 합니다 ──┘

서버 쪽 포트를 정하면 맥북 쪽 포트도 따라 정해집니다. 계층이 새는 셈입니다.

대안은 콜백 오리진을 동적으로 만드는 것인데, OAuth 공급자에 사전 등록이 필요한 구조라 결국 등록 목록을 관리해야 합니다. 복잡도가 더 크다고 보고 감수했습니다. 대신 이 제약을 기동 스크립트 출력에 적어뒀습니다. 사람이 기억하지 않아도 되도록 하기 위해서입니다.


4. 셋째 단은 “적합”이 아니라 타협입니다

화면이 운영 API에 직결입니다. 저장·발행·삭제 버튼이 실제 운영 데이터를 바꿉니다.

원래 목적이 “디자이너가 실제 데이터로 화면을 본다”였으니 그 목적에는 맞습니다. DB를 따로 세팅하고 시드를 만드는 비용을 안 치르고 진짜 화면을 봅니다.

필요한 사용은 열고, 직접 편집은 막았습니다

디자이너와 마케터도 실제 화면을 확인하고 각자의 업무에 필요한 API를 사용할 수 있어야 했습니다. 그렇다고 운영 DB에 직접 접속해 데이터를 조회하거나 수정할 권한까지 줄 필요는 없었습니다. 그래서 사람에게는 허용된 API 경로만 열고, DB 자격증명과 직접 편집 권한은 개발자와 운영 영역에 남겨두었습니다.

이 구분은 처음부터 의도한 경계입니다. 화면 확인과 콘텐츠 업무는 API를 통해 가능하지만, DB를 직접 만지거나 운영 데이터를 임의로 바꾸는 일은 다른 권한으로 분리했습니다. 문제가 생겨도 영향 범위를 API 수준에 가둘 수 있고, 디자이너·마케터가 업무를 진행하는 데 불필요한 운영 권한을 전달하지 않아도 됩니다.

다만 API가 운영 데이터를 바꾸는 기능까지 포함한다면, API 사용 자체가 곧 쓰기 작업이 될 수 있습니다. 따라서 저장·발행·삭제처럼 영향이 큰 기능은 별도의 권한과 확인 절차가 필요합니다. 운영 DB에 직접 접근하지 않는 것만으로 모든 쓰기 위험이 사라지는 것은 아닙니다.

이 경계 때문에 e2e 테스트는 현재 화면 확인 경로 위에 올릴 수 없습니다.


5. e2e는 이 위에 못 얹습니다

e2e는 쓰기를 자동으로, 반복해서, 사람 눈 없이 합니다. 글을 만들고 발행하고 지웁니다.

마지막 방어선이 “사람의 주의”인 구조에 그걸 올리면 방어선이 사라집니다. 게다가 실패하면 재실행하니 같은 변경이 반복해서 운영 데이터에 닿을 수 있습니다.

e2e는 4단이 아니라 나중에 붙일 별도 갈래입니다

처음엔 단을 하나 더 붙이는 문제로 봤습니다. 아니었습니다. 직렬로 붙는 게 아니라 갈라지는 구조고, e2e 갈래는 오히려 짧습니다.

┌─ next dev :3100 ─▶ 8787 터널 ─▶ 운영 api ─▶ 운영 DB
맥북 ─ssh─ 서버 ─┤ (화면 보기 · 지금)
│
└─ next dev :39xx ─▶ 로컬 api ─▶ 로컬 db/redis
▲ playwright가 여기서 직접 돕니다 (e2e · 앞으로)

e2e 갈래에는 맥북도 운영 서버도 없습니다.

  • 맥북이 필요 없습니다. playwright가 headless로 서버 안에서 도니까 사람이 볼 필요가 없고, 그러니 SSH 터널도 안 씁니다.
  • 운영 서버가 필요 없습니다. 운영 api를 아예 안 부릅니다. 터널이 떠 있든 없든 무관합니다.

단으로 세면 3단이 아니라 2단입니다. 서버 안에서 시작해서 서버 안에서 끝나고, 네트워크를 한 번도 안 나갑니다.

무엇이 갈리고 무엇이 같은가

항목화면 보기 (지금)e2e (앞으로)
코드같은 레포같은 레포 (작업 폴더 하나)
admin 포트계정별 배정 포트별도 번호
API_BASE127.0.0.1:8787 → 운영로컬 api 컨테이너
DB운영로컬 db
맥북 SSH 터널필요불필요
운영 서버직결닿지 않음

포트가 다르고 API_BASE가 다르니 동시에 떠 있어도 서로 안 건드립니다. 한 사람이 운영 화면을 보는 동안 같은 장비에서 e2e가 로컬 DB를 상대로 돌 수 있습니다. dev 서버 3.7GB에 컨테이너 세 개 얹어도 96GB면 여유가 큽니다.

준비할 건 사실 둘입니다

docker-compose.yml에 db·redis·api가 이미 정의돼 있습니다. 길은 설계돼 있는데 깔려 있지 않은 상태입니다.

  1. 서버에 docker를 설치합니다. 지금 없습니다.
  2. 그 세 개만 올리고 API_BASE를 그쪽으로 돌립니다.

2번이 제일 중요합니다. 운영 터널과 e2e 스택이 같은 API_BASE를 쓰는 순간 운영 데이터에 닿을 수 있습니다. 포트를 따로 주고, e2e 쪽은 운영 터널이 떠 있든 없든 무관하게 도는 구조여야 합니다.

테스트 DB 시드와 playwright 하네스는 그 다음입니다. 위 둘이 되면 “운영에 안 닿는 화면”이 생기고, 거기서부터는 평범한 테스트 작업입니다.

다만 이건 지금 당장 해야 할 일은 아닙니다. 현재 팀과 서비스 규모에서는 e2e 환경을 먼저 세팅하는 것보다 코딩과 화면 확인을 안정적으로 굴리는 일이 우선입니다. e2e는 운영 데이터에 닿지 않는 별도 스택으로 추후 별도 세팅하면 됩니다. 지금은 구조만 미리 분리해두고, 테스트가 필요한 시점에 로컬 API·DB·Redis와 Playwright를 얹을 계획입니다.


마무리하며

구간판단
맥북 → 서버✅ 닫힘 · 통로 분리 · 무거운 쪽이 힘센 쪽에
서버 → 운영⚠️ 타협입니다. 화면 확인엔 충분하고 쓰기엔 위험합니다
e2e❌ 지금 위에 얹으면 안 됩니다. 셋째 단을 바꿔 끼워야 합니다

바꿀 게 있다면 셋째 단뿐이고, 그것도 e2e를 시작할 때 이야기입니다. 코딩과 화면 확인에는 손댈 필요가 없습니다.

다만 이 구조가 유일한 답은 아닙니다. 회선이 느린 곳에서 일한다면 HMR 왕복이 매번 네트워크를 타는 게 거슬릴 수 있습니다. 저희가 이쪽을 고른 이유는 단순합니다. 코딩 에이전트를 서버에서 돌리고 있어서 코드와 에이전트가 같은 자리에 있어야 했습니다.

댓글