[DRAFT] GBrain을 AWS에 프라이빗하게 배포하고 노트북에서 쓰는 법
이 글은 garrytan/gbrain v0.51.6.0을 코드 수정 없이 AWS CDK만으로 서울 리전에 올리고, 맥북의 CLI와 Claude Desktop에서 실제로 사용하기까지의 기록입니다. 처음 며칠은 SSM 터널로만 접속했고(v1), 그다음 CloudFront VPC Origin으로 공개 URL 하나만 열었습니다(v2). 모든 수치는 2026-09-22~28에 직접 측정한 값이고, 모든 명령은 실제로 실행해 동작을 확인한 것입니다.
1. 왜 이걸 했나
Claude, Kiro, Cursor 같은 AI 에이전트를 매일 쓰다 보면 한 가지가 계속 아쉽습니다. 대화가 끝나면 다 잊는다는 것. 어제 결정한 내용, 지난주 회의 메모, 어떤 고객이 뭘 물어봤는지를 매번 다시 붙여넣어야 합니다.
GBrain은 이 문제를 푸는 오픈소스입니다. 마크다운 페이지를 PostgreSQL + pgvector에 저장하고, 어떤 에이전트든 MCP(Model Context Protocol)로 붙어서 검색하고, 기억하고, 인용하며 답하게 해 줍니다. 밤에는 dream이라는 배치가 돌아 중복을 정리하고 사실을 추출합니다. Y Combinator의 Garry Tan이 자기 에이전트용으로 만들어 공개했고, 15만 페이지 규모로 운영 중이라고 합니다.
문제는 어디에 둘 것인가입니다. 업스트림 문서는 Supabase나 Railway 같은 SaaS, 혹은 Tailscale로 노트북을 공개하는 방법을 안내합니다. 저는 세 가지 조건이 있었습니다.
- 데이터는 내 AWS 계정 안에, 서울 리전에 머물러야 한다.
- 서버(ALB, 컨테이너, DB)에 공개 IP가 하나도 없어야 한다. 인터넷에서 들어오는 길은 없거나, 있다면 WAF 뒤의 CloudFront 하나여야 한다.
- IAM 사용자 정적 액세스 키를 하나도 만들지 않아야 한다.
3번은 회사 계정 정책이지만, 개인 계정에서도 그렇게 하는 것이 맞습니다. 이 글은 그 조건을 전부 만족시키면서 GBrain을 올린 결과물입니다. 처음(v1)에는 2번을 "인터넷 입구 0"으로 잡고 SSM 터널로만 썼는데, 폰이나 Claude 커넥터에서도 쓰고 싶어져서 며칠 뒤 CloudFront VPC Origin으로 입구 하나를 열었습니다(v2). 그 과정에서 배운 것이 이 글의 절반입니다.
2. 무엇이 만들어지나
노트북 / 폰 / Anthropic 서버 AWS
┌────────────────────────┐ HTTPS ┌ CloudFront (WAF, /admin* 차단) ─ VPC Origin ─▶ 내부 ALB ─▶ Fargate ┐
│ gbrain CLI │──────────▶│ ┌─ gbrain serve --http │
│ Claude Desktop / 커넥터 │ │ ap-northeast-2, 전부 프라이빗 서브넷 └─ litellm ─▶ Bedrock │
└────────────────────────┘ │ RDS PostgreSQL 16 + pgvector (KMS, TLS) │
┌────────────────────────┐ SSM 터널 │ EventBridge 07:00 UTC ─▶ dream 태스크 · 알람 8개 ─▶ SNS ─▶ 이메일 │
│ 브라우저 /admin (관리) │──────────▶│ bastion(t4g.nano) ─▶ 내부 ALB (v1 경로, 관리 전용으로 유지) │
└────────────────────────┘ └──────────────────────────────────────────────────────────────────┘
| 구성 요소 | 선택 | 이유 |
|---|---|---|
| 컴퓨트 | ECS Fargate 1태스크 (1 vCPU / 3 GiB), 루트 FS 읽기 전용 | 서버 관리 없음. gbrain은 상태를 전부 DB에 두므로 컨테이너는 언제든 버려도 됨 |
| DB | RDS PostgreSQL 16.13 + pgvector 0.8.1, db.t4g.medium 50 GiB | 업스트림 권장 엔진. isolated 서브넷, rds.force_ssl=1, 고객 KMS 키 |
| LLM | LiteLLM 사이드카 -> Amazon Bedrock | gbrain은 OpenAI 호환 API를 기대. LiteLLM이 Bedrock으로 번역하고 태스크 롤로 인증 -> API 키 0개 |
| 모델 | 채팅 apac.anthropic.claude-3-5-sonnet-20241022-v2:0, 임베딩 amazon.titan-embed-text-v2:0 (1024차원) |
둘 다 APAC 안에서만 라우팅. 최신 Claude는 서울에서 global.* 프로파일만 있어 데이터가 리전 밖으로 나갈 수 있어 제외 |
| 입구 (v1) | 내부 ALB + SSM 포트포워딩 bastion | 공개 IP 없음. 접속 권한 = IAM 권한. v2 이후에도 관리 화면(/admin) 전용으로 남김 |
| 입구 (v2) | CloudFront(기본 도메인) → VPC Origin → 같은 내부 ALB, 앞에 WAF | ALB를 공개로 바꾸지 않고도 CloudFront가 프라이빗 서브넷의 ALB에 직접 붙음. 인터넷 게이트웨이 경유 인바운드 여전히 0. 도메인·인증서 없이 *.cloudfront.net으로 시작 |
| 운영 | dream 스케줄 태스크, 알람 6개, Budgets, VPC Flow Logs | 밤에 알아서 돌고, 깨지면 메일 |
CDK 스택은 v1 4개(Network -> Data -> Service -> Ops)에 v2에서 us-east-1 2개(EdgeWaf, EdgeAlarms -- CloudFront용 WAF와 그 지표의 알람은 버지니아에만 만들 수 있습니다)가 붙습니다. 비용은 v1 월 약 $195 실측 $210, v2가 WAF ≈ $10을 더합니다(CloudFront·VPC Origin·기본 인증서는 무료 사용량 안). 절반 이상이 RDS($80)와 NAT 게이트웨이($45)입니다.
3. 배포 — 실제로 걸린 시간
cp .env.example .env # 계정 ID, 리전, 모델 ID, 이미지 다이제스트, 알람 이메일
npm ci && npx cdk synth # 4스택 합성 + 75개 단위 테스트
npx cdk deploy GbrainNetwork GbrainData # 17분 (RDS 생성이 14분)
npx cdk deploy GbrainService GbrainOps # 13분 (이미지 빌드·푸시 포함)
두 가지 설계 선택이 배포를 단순하게 만들었습니다.
DB 초기화를 CloudFormation 커스텀 리소스로. pgvector 확장 설치와 애플리케이션 롤 생성을 Lambda가 배포 중에 수행합니다. 사람이 psql로 들어갈 일이 없고, 로그에 비밀이 남지 않도록 결과만 (vector extension ok, BYPASSRLS ok) 찍습니다.
컨테이너 엔트리포인트가 gbrain init을 매번 실행. 첫 태스크는 스키마 마이그레이션(v159)을 적용하고, 이후 태스크는 Brain ready. N pages만 찍고 지나갑니다. 멱등이라는 것을 실측으로 확인했기 때문에 "초기화 완료 여부"를 어디에 기록할 필요가 없습니다.
**v2 공개 인그레스(09-28)**는 .env에 플래그 하나(PUBLIC_INGRESS=true)를 켜고 cdk deploy 4스택으로 19분 20초. 대부분이 CloudFront VPC Origin 생성(9분 35초)과 Distribution(2분 48초)이었고, 태스크 정의에 GBRAIN_PUBLIC_URL이 들어가면서 서비스가 한 번 롤링 교체됐습니다(4분 25초). 플래그가 꺼져 있으면 v1 템플릿은 바이트 단위로 동일합니다 -- 테스트가 그것을 고정합니다.
실제로 부딪힌 것들
문서만 보고 짠 코드가 실제 배포에서 깨진 지점들입니다. 같은 구성을 하실 분들이 시간을 아끼시길 바라며 그대로 적습니다.
| 증상 | 원인 | 해결 |
|---|---|---|
self signed certificate in certificate chain |
gbrain 런타임인 Bun 1.3은 시스템 CA 저장소를 안 보고, postgres.js는 URL의 sslrootcert를 무시 |
Dockerfile에 RDS CA 번들을 넣고 NODE_EXTRA_CA_CERTS로 지정 |
unrecognized configuration parameter "sslrootcert" |
postgres.js가 모르는 URL 파라미터를 그대로 서버 세션 설정으로 넘김 | URL에서 제거, ?sslmode=verify-full만 남김 |
ECS Exec agent STOPPED |
루트 FS를 읽기 전용으로 잠갔더니 SSM 에이전트가 쓸 곳이 없음 | /var/lib/amazon, /var/log/amazon에 임시 볼륨 마운트 |
Bedrock model_not_found + "marked by provider as Legacy" |
인퍼런스 프로파일 상태는 ACTIVE인데 실제 호출은 거부 | 프로파일 목록만 믿지 말고 converse 1회 호출로 확인. 모델 교체 |
dream이 ANTHROPIC_API_KEY 요구 |
업스트림 기본값이 게이트웨이(LiteLLM)를 안 씀 | gbrain config set agent.use_gateway_loop true |
| Budgets 재배포 실패 "same name but a different internalId" | 구독자 변경 = 리소스 교체인데 이름이 고정 | 구독자를 SNS 토픽으로 고정하고 토픽이 이메일로 fan-out |
배포 후 "잘못된 태스크 정의를 배포하면 어떻게 되나"도 확인했습니다. 일부러 DB 포트를 틀리게 넣어 배포하자 ECS 서킷 브레이커가 10분 19초에 자동 롤백했고, 브레인의 페이지는 그대로였습니다.
| (v2) 공개 URL이 전부 504 | VPC Origin을 거친 요청은 ALB에 CloudFront의 공개 IP(54.182.x.x)를 소스로 도착. 서브넷 CIDR로 연 SG 규칙은 무효 -- Flow Logs REJECT로 확인 | ALB SG에 관리형 접두사 목록 com.amazonaws.global.cloudfront.origin-facing(리전별 pl-…) 허용. 16초 재배포 |
| (v2) 공개 URL 전환 후 모든 클라이언트 401 | 토큰이 resource=<issuer>/mcp에 묶여 있어 issuer가 localhost → CloudFront로 바뀌면 전부 무효 | 토큰 재발급. OAuth 클라이언트(id/secret)는 유효해서 init만 다시 하면 됨 |
| (v2) CloudFront 지표 알람을 서울 스택에 만들면 지표가 없음 | CloudFront·WAF 지표는 us-east-1, 알람과 SNS 액션은 지표와 같은 리전이어야 함 | us-east-1에 알람 스택 하나 더 |
| (v2) 크로스리전 참조에 Secrets Manager 동적 참조를 얹으면 실패 | {{resolve:secretsmanager}}를 SSM 파라미터 경유 토큰과 중첩 불가 | 헤더 검증용 Secret을 ALB와 같은 리전에 |
4. 노트북에서 쓰기
여기서부터가 매일 하는 일입니다.
4.1 준비물 (한 번)
- AWS CLI v2 +
session-manager-plugin(brew install awscli session-manager-plugin) -- v2 이후에는 관리 화면(/admin)에 들어갈 때만 - 그 계정에
ssm:StartSession(터널)과ecs:ExecuteCommand(최초 클라이언트 등록) 권한 - Bun 1.3.11+ (
curl -fsSL https://bun.sh/install | bash) - gbrain CLI — npm에 없습니다. npm의
gbrain은 이름만 같은 무관한 패키지라 설치하면 진짜 바이너리를 가립니다. 서버와 같은 태그로 GitHub에서 설치합니다.
bun install -g github:garrytan/gbrain#v0.51.6.0
gbrain --version # 0.51.6.0
4.2 터널 열기 (v1에서는 매번, v2에서는 관리할 때만)
v1에서는 이것이 매일 첫 번째 일이었습니다. 별도 터미널 탭에 띄워 두고 닫지 않습니다. 로컬 포트는 반드시 3131 -- 서버가 OAuth 발급자 주소를 http://localhost:3131로 고정해서 돌려주기 때문에, 3132로 열면 토큰 요청이 400으로 실패합니다(직접 확인했습니다). v2에서 --public-url을 설정하면 이 제약이 사라지고, 터널은 /admin 관리 화면과 Claude 커넥터의 OAuth 동의 화면에만 필요합니다.
aws ssm start-session --target <bastion-instance-id> \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters host=<internal-alb-dns>,portNumber=80,localPortNumber=3131 \
--region ap-northeast-2
curl -s localhost:3131/health
# {"status":"ok","version":"0.51.6.0","engine":"postgres"}
이 명령은 CDK 스택 Output(TunnelCommand)에 완성된 형태로 나오므로 외울 필요는 없습니다.
4.3 자격증명 만들기 (한 번)
서버 안에서 gbrain auth 명령을 실행해야 합니다. 컨테이너에 SSH가 없으니 ECS Exec을 씁니다. 노트북 터미널에서 그대로 치면 AWS가 컨테이너 안에서 대신 실행해 결과를 보여 줍니다.
TASK=$(aws ecs list-tasks --cluster gbrain-dev --service-name gbrain-dev-serve \
--desired-status RUNNING --region ap-northeast-2 --query 'taskArns[0]' --output text | awk -F/ '{print $NF}')
# CLI용 OAuth 클라이언트 (id + secret)
aws ecs execute-command --cluster gbrain-dev --task "$TASK" --container gbrain --interactive --region ap-northeast-2 \
--command 'gbrain auth register-client mylaptop --grant-types client_credentials --scopes write'
# Claude Desktop 등 MCP 클라이언트용 Bearer 토큰
aws ecs execute-command --cluster gbrain-dev --task "$TASK" --container gbrain --interactive --region ap-northeast-2 \
--command 'gbrain auth create claude-desktop --scopes read,write'
출력되는 secret과 토큰은 그 자리에서만 보입니다. 서버는 해시만 저장하니 바로 저장하세요. 잃으면 새로 만들면 됩니다.
자동화 도구에서 이 명령을 돌리면 TTY가 없어
script -qec "…" /dev/null래퍼가 필요한데, macOS의 BSDscript에는-c가 없습니다. 사람이 터미널에서 칠 때는 래퍼 없이 위 명령 그대로가 맞습니다.
4.4 CLI 초기화와 일상 명령
export GBRAIN_HOME=~/.gbrain-seoul # 다른 브레인 설정과 분리
export GBRAIN_REMOTE_CLIENT_ID=gbrain_cl_…
export GBRAIN_REMOTE_CLIENT_SECRET=gbrain_cs_…
# v1 (터널): --issuer-url http://localhost:3131 --mcp-url http://localhost:3131/mcp
# v2 (공개 URL, 터널 불필요):
gbrain init --mcp-only --issuer-url https://<dist-id>.cloudfront.net --mcp-url https://<dist-id>.cloudfront.net/mcp
# ✓ discovery ✓ token ✓ MCP initialize → Thin-client mode configured
v1에서 v2로 넘어갈 때 한 번 다시 init해야 합니다. 토큰은 발급자(issuer) 주소에 묶여 있어 전부 무효가 되지만, 클라이언트 id/secret은 그대로 쓰면 됩니다.
이후는 세 명령이 전부입니다.
gbrain capture "오늘 회의: 예산 필터를 Project 태그로 바꾸기로 함"
gbrain search "예산"
gbrain think "예산 관련해서 결정한 게 뭐였지?"
search는 관련 페이지 목록을 돌려주고, think는 그 페이지들을 근거로 **답변 + 인용 + "브레인이 모르는 부분"**을 써 줍니다. "뭘 결정했었지?"에는 think가 맞고, "그 문서 어디 있지?"에는 search가 맞습니다.
5. Claude Desktop에 붙이기
GBrain에는 공식 데스크톱 앱이 없습니다. 설계 철학이 "UI는 여러분이 이미 쓰는 에이전트, GBrain은 그 뒤의 기억"이기 때문입니다. 그래서 Claude Desktop이 사실상 GBrain의 GUI입니다.
v1(터널)에서의 함정. Claude Desktop의 Settings > Connectors(예전 이름 Integrations)에 http://localhost:3131/mcp를 넣으면 안 됩니다. 커넥터는 Anthropic 서버를 경유해 접속하므로 노트북에서만 보이는 주소에 닿지 못합니다. 그래서 v1에서는 로컬 stdio 브리지(mcp-remote)를 썼습니다. Claude Desktop이 노트북에서 작은 프로세스를 띄우고, 그 프로세스가 터널을 타고 서버로 갑니다.
v2(공개 URL)에서는 셋 다 됩니다. 실제로 검증한 순서대로:
- 설정 파일 +
mcp-remote브리지 (기본). v1과 같은 방식인데 URL과 토큰만 바뀌고 터널이 필요 없습니다.~/Library/Application Support/Claude/claude_desktop_config.json(사내 배포 빌드는Claude-3p/)에 기존 내용은 그대로 두고mcpServers만 추가합니다.
{
"preferences": { "...기존 내용 그대로..." },
"mcpServers": {
"gbrain-seoul": {
"command": "npx",
"args": [
"-y", "mcp-remote@latest",
"https://<dist-id>.cloudfront.net/mcp",
"--header", "Authorization:Bearer ${GBRAIN_TOKEN}"
],
"env": { "GBRAIN_TOKEN": "4.3에서 받은 토큰" }
}
}
}
순서: Claude Desktop 완전 종료(Cmd+Q) -> 파일 편집 -> chmod 600 -> 앱 실행 -> 새 대화에서 "내 브레인에서 배포 검증 찾아줘". 도구 목록에 gbrain-seoul이 뜨면 성공입니다. 안 보이면 (a) which npx 절대경로를 command에(Claude Desktop은 로그인 셸 PATH를 물려받지 못함), (b) 사내 빌드의 조직 정책("disabled by organization")을 확인합니다. 같은 JSON 블록은 Kiro(~/.kiro/settings/mcp.json), Cursor 등 stdio MCP를 지원하는 어떤 클라이언트에도 그대로 씁니다.
-
Connectors > Add custom connector + Request headers. URL을 넣고 Authentication을 "No sign-in"으로, Request headers에
authorization: Bearer <토큰>(앞의Bearer와 공백 포함). 가장 깔끔하지만 Anthropic이 베타로 일부 조직에만 열어 두어 대화상자에 이 섹션이 없을 수 있습니다. -
Connectors + 자체 OAuth 클라이언트. 서버에
authorization_code+ PKCE 공개 클라이언트를 등록하고(redirect URI는https://claude.ai/api/mcp/auth_callback과https://claude.com/api/mcp/auth_callback둘 다) Client ID만 입력합니다. 여기서 재미있는 함정이 하나 나옵니다. GBrain의/authorize는 동의 화면을/admin/?oauth_request=…로 리다이렉트하는데, 우리는 CloudFront에서/admin*을 403으로 막아 두었습니다(owner 로그인·magic link를 인터넷에 내지 않기 위해). 그래서 403 페이지가 뜨면 주소창의 호스트만localhost:3131로 바꿔 터널로 열고 승인합니다. 커넥터 등록 때 한 번이고, 이후에는 refresh 토큰으로 알아서 갱신됩니다./admin비노출을 유지하는 대가로 받아들일 만한 절차라고 판단했습니다.
6. 알아서 돌아가는 것
- 매일 16:00 KST dream. 페이지 정리, 사실 추출, 임베딩, 중복 제거. 첫 수동 실행은 53초, 리포트
status: ok. 파일시스템 브레인 디렉터리가 없는 DB-only 구성이라 lint/sync 단계는no_brain_dir로 건너뛰는데, 이것이 정상입니다. - 실패하면 메일. dream 태스크가 exit 0이 아니면 EventBridge -> SNS -> 이메일. 일부러 잘못된 플래그로 돌려 알림이 오는지까지 확인했습니다. ALB 5xx, 태스크 0개, RDS CPU/메모리/스토리지 알람 6개도 같은 토픽으로 옵니다.
- 인터넷 쪽 방어. WAF 관리형 룰(Common, IP Reputation) + IP당 5분 300요청 rate limit, 그 뒤 GBrain 자체 OAuth. CloudFront 5xx율과 WAF 차단 급증은 별도 알람. 일부러 504를 낸 첫 배포 직후
CloudFront5xx알람이 정확히 한 번 울었습니다. - 비용 멈추기.
aws ecs update-service --desired-count 0으로 Fargate만 내리면 데이터는 그대로 두고 월 $130(RDS·NAT·ALB·bastion·WAF)만 남습니다. 되살리면 2~3분 뒤 페이지가 그대로 나옵니다.
7. 안 되는 것과 다음
첨부파일(gbrain files upload)은 꺼져 있습니다. GBrain의 S3 저장소 코드가 정적 액세스 키(accessKeyId/secretAccessKey)만 받고, 없으면 생성자에서 예외를 던집니다. 컨테이너에는 IAM 롤(임시 자격증명)이 있지만 코드가 그걸 쓰려 하지 않습니다. 정적 키를 만들지 않는 것이 조건이므로 v1에서는 파일 기능을 포기했습니다.
고치는 방법은 작습니다. 두 키가 모두 없을 때 S3Client에 credentials를 넘기지 않으면 AWS SDK가 기본 체인으로 롤을 찾아 씁니다. 20줄 정도의 변경과 판별 테스트를 업스트림에 PR로 제출해 두었습니다(garrytan/gbrain#5344). 반영된 릴리스가 나오면 .env의 GBRAIN_REF를 올리고 S3 버킷 + 태스크 롤 권한을 CDK에 추가하는 것으로 정적 키 없이 첨부파일이 켜집니다.
그 외 제한:
- 공개 URL이 CloudFront 기본 도메인(
*.cloudfront.net)이라 뷰어 최소 TLS를 1.0에서 올릴 수 없고, Distribution이 재생성되면 주소와 함께 모든 토큰이 바뀝니다. 다음 단계는 Route 53 + ACM으로 커스텀 도메인을 붙이는 것인데 구조 변경 없이domainNames와 인증서만 추가하면 됩니다. /admin관리 화면은 터널 전용 — 의도한 것이지만 Claude 커넥터 OAuth 동의 때 한 번 번거롭습니다(5장)- Fargate 태스크 1개 — 재배포 중 2~3분 끊김 (개인용이라 감수)
- 최신 Claude(Sonnet 4.x)는 서울에서 글로벌 프로파일만 있어 제외 —
.env한 줄로 바꿀 수 있지만 데이터가 APAC 밖으로 나갈 수 있다는 점을 알고 바꿔야 함
8. 정리
| 배포 시간 | v1 약 30분 (RDS 14분이 대부분) + v2 약 20분 (VPC Origin 10분) |
| 월 비용 | v1 ≈ $210 실측, v2 +≈ $10 (Fargate 정지 시 ≈ $130) |
| 인터넷 인바운드 | v1: 0 (SSM 터널). v2: CloudFront 하나 (WAF, VPC Origin, /admin 차단). ALB·컨테이너·DB 공개 IP는 여전히 0 |
| 정적 AWS 키 | 0 (Bedrock·Secrets·로그 전부 태스크 롤) |
| 데이터 리전 | 서울 유지 (APAC 내 라우팅 모델만 선택, CloudFront는 캐시 없이 전달만) |
| 잘못된 배포 | 서킷 브레이커 10분 자동 롤백, 데이터 무손실 확인 |
| 매일 하는 일 | v1: 터널 1개 열기. v2: 없음 |
GBrain을 "내 계정 안에, 문 닫고, 키 없이" 올리는 것은 가능했고, 나중에 문을 하나 여는 것도 ALB를 공개로 바꾸지 않고 할 수 있었습니다. 실제로 부딪힌 문제는 GBrain보다 Bun의 TLS 처리, 읽기 전용 FS와 SSM 에이전트, Bedrock 레거시 모델, VPC Origin의 소스 IP처럼 주변에서 나왔습니다. CDK 코드와 상세 운영 문서(배포 절차, 21개 리스크 표, 사용 가이드)는 정리 후 공개할 예정입니다.