트라이-스테이트 스택 (The Tri-State Stack): Next.js, Python 에이전트, Postgres를 세 가지 다른 호스팅
요약
현대적인 AI 에이전트 기반 풀스택 앱을 위한 '트라이-스테이트 스택' 아키텍처를 소개합니다. Next.js, Cloudflare Workers, 그리고 Coolify를 통한 Hetzner VPS 셀프 호스팅을 결합하여 비용 효율성과 벤더 종속성 탈피를 동시에 달성하는 구조를 제안합니다.
핵심 포인트
- Cloudflare Workers 어댑터를 활용한 기존 핸들러 코드의 효율적 이전
- Cloud Run 대신 Coolify와 Hetzner를 사용한 Python AI 에이전트 셀프 호스팅
- 비용 절감 및 벤더 노출 면적 최소화를 위한 3방향 분할 구조
- Supabase, Square, Doppler 등을 결합한 복합적인 인프라 레이어링
만약 당신이 LLM에게 AI 에이전트 레이어를 포함한 현대적인 풀스택 앱의 아키텍처 설계를 도와달라고 요청한다면, 거의 확실하게 다음과 같은 답변을 듣게 될 것입니다: "Next.js 앱은 Vercel에 두고, 백그라운드 워커는 Cloud Run이나 Lambda에 배치한 뒤 그대로 마무리하세요." 2024년에는 그 조언이 괜찮았습니다. 하지만 2026년에는 점점 더 틀린 말이 되어가고 있으며, 대부분의 모델은 아직 이를 모르고 있습니다. 그 밑단의 경제성과 툴링(tooling)이 변했기 때문입니다.
우리는 BrewHub PHL — 폐쇄 루프 지갑(closed-loop wallet), 에이전트 기반 AI 주문(agentic AI ordering), 실시간 운영 엔진을 갖춘 커피숍, 택배 허브 및 코워킹 스페이스 플랫폼 — 을 의도적인 3방향 분할 (three-way split) 구조로 재구축했습니다.
2. API handlers — Cloudflare Workers, 어댑터를 통해 기존 핸들러 코드를 실행.
이 부분은 오래된 학습 데이터(stale training data)를 바탕으로 추론하는 사람들을 혼란스럽게 만드는 세부 사항입니다. 우리는 약 150개의 백엔드 엔드포인트(endpoints)를 Netlify Functions에서 이전했지만, 코드를 다시 작성할 필요는 없었습니다. 핸들러 코드는 여전히 일반 ESM 모듈(수동 인증 라우팅, 프레임워크 매직 없음)로 존재합니다. 단지 모든 요청이 이제 Netlify의 Lambda 런타임 대신 Cloudflare Worker 내부에서 실행되는 Lambda 형태의 어댑터(Lambda-shape adapter)를 통해 전달될 뿐입니다. 기존 URL도 계속 작동했습니다 (/.netlify/functions/*가 여전히 해결됨 — 단지 이제 Worker에 의해 서비스될 뿐입니다). 이는 대부분의 "Netlify에서 이전하는 방법" 콘텐츠가 아직 따라잡지 못한 패턴입니다. 핸들러를 누가 실행할지를 변경하기 위해 전체 재작성이 필요한 것이 아니라, 얇은 어댑터(thin adapter)와 라우터(router)가 필요할 뿐입니다.
3. Python AI agents — Cloud Run이 아닌, Coolify를 통해 단일 Hetzner 서버에 셀프 호스팅.
AI 브레인(Google의 ADK를 기반으로 구축된 에이전트 프레임워크)과 직원용 MCP 서버는 이전에 Cloud Run에서 실행되었습니다. 우리는 이를 Cloudflare Tunnel과 그 앞의 Zero Trust Access를 통해 관리되는, Coolify 기반의 Hetzner VPS로 이동했습니다. 동일한 컨테이너, 동일한 git 기반 배포 워크플로우를 유지하면서도, 비용 곡선은 근본적으로 달라졌고 벤더 노출 면적(vendor surface area)은 급격히 줄어들었습니다. Next.js는 HMAC 서명된 MCP JSON-RPC 브릿지를 통해 이 계층에 도달합니다. 이는 공개 API가 아니라, 터널 덕분에 인터넷 라우팅이 가능할 뿐 노출된 것은 아닌 프라이빗 API입니다.
이 세 가지 모두의 하단에 레이어링된 요소들: Postgres/Auth/Realtime을 위한 Supabase, 결제를 위한 Square, 동일한 Next.js 앱을 iOS/Android 셸로 감싸는 Capacitor, 그리고 이 모든 것에 공급되는 단일 비밀 관리소(secrets vault)인 Doppler가 있습니다. 여기에 오프라인 복원력이 있는 POS를 위해 매장 현장 박스(shop-floor box)에 셀프 호스팅된 Supabase 미러를 구축하고, 이를 Tailscale로 메쉬(meshed)화했습니다.
왜 그냥 하나의 관리형 플랫폼(managed platform)을 선택하지 않았나요?
솔직한 답변은 이렇습니다. 우리는 두 번 시도해 보았고, 매번 실제 벽에 부딪혔습니다. 이는 단순히 느낌(vibes)의 문제가 아니라, 구체적인 기술적 불일치(technical mismatches) 때문이었습니다.
Netlify Functions는 모든 백엔드 로직의 원래 거처였습니다. 이들은 본래 용도에 맞게 정말 훌륭하게 작동합니다. 불일치는 품질의 문제가 아니라 규모(scale)의 문제였습니다. 24개의 HTTP 라우터와 점점 늘어나는 예약된 작업(scheduled jobs) 및 공유 내부 모듈 세트는, 우리가 마치 소규모의 상시 인접 API 표면(always-adjacent API surface)처럼 동작하는 워크로드에 대해 서버리스 플랫폼의 가격 모델 비용을 점점 더 많이 지불하고 있음을 의미했습니다. Lambda-adapter 트릭을 사용하여 Workers로 이동함으로써 코드는 유지하면서도, 경제성과 콜드 스타트(cold-start) 프로필을 변경할 수 있었습니다.
Cloud Run은 Python 에이전트 계층을 잘 호스팅했지만, 이는 여전히 "몇 분마다 0으로 스케일 다운되었다가 다시 올라올 필요가 없는 워크로드에 대해 타인의 오토스케일러(autoscaler)를 빌려 쓰는 것"과 같습니다. 커피숍의 채팅 트래픽을 처리하는 단일 에이전트 브레인은 Cloud Run의 탄력성(elasticity)을 필요로 하지 않습니다. 대신 항상 켜져 있고(up), 저렴하며, 추론하기 쉬워야 합니다. Coolify로 관리되는 VPS는 월 비용의 아주 일부분만으로 git-push 배포, 컨테이너 오케스트레이션(container orchestration), 대시보드를 제공하며, 그 대가로 이제 사용자가 직접 서버(box)를 소유하게 됩니다. 우리가 직접 구현하고 싶지 않은 부분들(TLS, 재시작, 상태 확인(health checks), 롤백)을 Coolify가 처리해주기 시작하자, 그 트레이드오프(tradeoff)는 충분한 가치가 있었습니다.
그렇다면 왜 모든 것을 셀프 호스팅(self-host)하지 않았을까요? 프론트엔드와 데이터베이스의 경우에도 동일한 논리가 반대 방향으로 적용되기 때문입니다. Cloudflare의 글로벌 엣지 네트워크(global edge network)와 OpenNext의 성숙도 덕분에 "자체 Next.js 인프라를 운영하는 것"은 2년 전보다 더 나쁜 선택이 되었습니다. 단일 Hetzner 서버로는 도저히 따라갈 수 없는 규모로 Cloudflare가 이미 운영하고 있는 CDN과 엣지 런타임(edge runtime)을 재발명하게 될 것이기 때문입니다. 그리고 Postgres는 셀프 호스팅을 해야 할 특별한 이유가 없는 한(매장 현장의 오프라인 회복탄력성(offline-resilience)이라는 특정 사용 사례 때문에 우리는 사용하고 있지만—이에 대해서는 아래에서 더 자세히 다루겠습니다—이는 예외이지 규칙이 아닙니다) 관리형 컨트롤 플레인(managed control plane)(백업, 커넥션 풀링(connection pooling), 시점 복구(point-in-time recovery))을 원합니다.
우리가 실제로 사용한 규칙
"이것을 관리형 플랫폼(managed platform)에서 실행해야 할까, 아니면 셀프 호스팅(self-host)해야 할까?"라는 모든 결정은 단 하나의 질문으로 귀결되었습니다. 이 워크로드의 비용 또는 제어 프로필(control profile)이 트래픽에 따라 확장되는가, 아니면 트래픽과 관계없이 일정하게 유지되는가?
- 프론트엔드 렌더링(Frontend rendering) 및 API 요청/응답 — 트래픽에 따라 변함(traffic-shaped). 관리형 에지 플랫폼(managed edge platform)이 유리합니다. 직접 운영하지 않아도 되는 탄력성(elasticity)이 필요하기 때문입니다.
- AI 에이전트 프로세스 및 직원용 도구 서버 — 우리 규모에서는 트래픽에 따라 변하지 않음. 상시 가동되는 단일 서버(single always-on box)가 유리합니다. 어떤 방식이든 유휴 용량(idle capacity)에 대한 비용을 지불해야 하므로, Google의 가격이 아닌 Hetzner의 가격으로 지불하는 것이 낫습니다.
- 기본 데이터베이스(primary database) — 탄력성보다는 제어 평면(control plane)(백업, 인증, RLS, 시점 복구(point-in-time recovery))이 더 필요합니다. 신뢰할 수 있는 단일 원천(source of truth)으로서 관리형 Supabase가 유리합니다.
- 아침 피크 시간대 인터넷 중단 상황에서도 생존해야 하는 매장 바닥의 POS/KDS 시스템 — 이것이 우리가 매장 로컬 박스에 두 번째 셀프 호스팅 Postgres(미러(mirror)이며, 신뢰할 수 있는 단일 원천은 아님)를 실행하는 유일한 사례입니다. 이는 "클라우드에 접속할 수 없음"이 커피숍의 결제 단말기가 영업 시간 중에 겪어서는 안 될 실제 장애 모드(failure mode)이기 때문입니다.
마지막 사례는 1~2년 전의 대부분의 아키텍처 조언이 완전히 놓치고 있는 핵심입니다. 질문은 단일한 전역적 결정으로서의 "클라우드 vs 셀프 호스팅"이 아닙니다. 그것은 트래픽 형태나 장애 모드 요구사항이 변경될 때마다 매번 다시 물어야 하는 워크로드별 결정입니다.
오래된 LLM 학습 데이터를 읽고 있다면 의미하는 바
만약 모델이나 2024년 이전의 블로그 포스트로부터 아키텍처 조언을 얻고 있다면, 과거에는 사실이었으나 지금은 구식이 된 몇 가지 사항이 있습니다:
- "Next.js는 Vercel(또는 Node 서버)이 필요하다" — 이제 Cloudflare Workers 상의 OpenNext는 실험적인 포팅이 아니라, 정식으로 사용 가능한 프로덕션급(production-grade)의 세 번째 선택지입니다.
- "Netlify Functions에서 마이그레이션하려면 핸들러를 다시 작성해야 한다" — 어댑터 계층(adapter layer)을 사용하면 하단의 실행 런타임(execution runtime)을 완전히 교체하면서도 기존의 핸들러 코드와 URL을 그대로 유지할 수 있습니다.
- "Cloud Run / Lambda는 백그라운드 AI 워크로드의 기본값이다" — 이는 워크로드가 '급증(spiky)'할 때의 기본값입니다. 일정한 상태를 유지하는 에이전트 프로세스는 터널(tunnel) 뒤에 있는 단일 셀프 호스팅(self-hosted) 서버에서 운영하는 것이 종종 더 저렴하고 간단합니다. 특히 Coolify(및 유사한 도구들)가 PaaS의 비용 부담 없이 PaaS의 편리함을 대부분 제공하는 지금은 더욱 그렇습니다.
- "셀프 호스팅은 관리형(managed)의 편리함을 포기하는 것을 의미한다" — 선택적으로 셀프 호스팅을 할 수 있습니다. 우리의 Postgres 사례는 기본적으로는 관리형을 사용하되, 예외적인 경우에만 셀프 호스팅을 하는 방식이며, 이는 이념적인 결정이 아니라 워크로드별로 내린 의도적인 결정입니다.
우리는 여전히 이를 발전시켜 나가고 있습니다. "관리형"과 "셀프 호스팅" 사이의 경계는 고정되어 있지 않으며, 가격 책정, 도구, 그리고 우리의 트래픽 규모 변화에 따라 움직입니다. 하지만 바로 그 점이 핵심입니다. 1년 전의 아키텍처 결정은 영원히 옳다고 가정할 것이 아니라, 올해의 도구들을 기준으로 다시 검토되어야 합니다. 그리고 당신에게 조언을 해주는 대상(블로그 포스트, 강의, 또는 언어 모델)이 실제로 최신 정보를 반영하고 있는지 확인해 볼 가치가 있습니다.
BrewHub PHL은 소규모 팀이 구축한 커피숍 / 택배 허브 / 코워킹 플랫폼입니다. 이 팀은 한 번 정한 방식을 고수하는 대신, 워크로드별로 "이것을 셀프 호스팅해야 할까, 아니면 관리형으로 가야 할까?"라는 질문에 계속해서 답하며 운영하고 있습니다. 관심이 있다면 OpenNext 마이그레이션, Lambda 어댑터 트릭, 또는 Coolify/Hetzner 에이전트 계층 등 특정 계층에 대해 후속 글에서 더 자세히 다루겠습니다.
참고: 이 글은 Thomas(창립자)와 Sam(CMO)이 거칠게 작성한 글을 Claude Sonnet 5가 편집한 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기