AI 가 만들고 preview 에 올립니다. 공개는 내가 합니다.
ChatGPT·Claude 웹에 Hangar 를 연결하면 설치 없이 바로 시작합니다. 더 큰 프로젝트나 이미지가 많은 사이트는 Claude Desktop 을 쓰는 것이 낫습니다. 어느 쪽이든 AI 는 나만 볼 수 있는 preview 주소에만 올리고, 모두에게 공개하는 버튼은 AI 에게 없습니다 — 내가 preview 를 확인하고 콘솔에서 한 번 누릅니다.
1웹에서 연결하기 (설치 없음)
가장 쉬운 방법입니다. 아무것도 설치하지 않고, 지금 쓰는 ChatGPT 나 Claude 웹 화면에 Hangar 를 커넥터로 추가합니다. 한 페이지짜리 사이트나 가벼운 수정에 알맞습니다. 대괄호 < > 부분은 내 값으로 바꿉니다.
1. 콘솔 가입 · 워크스페이스
console.apps.2tl.io 를 엽니다. 계정이 없으면 로그인 화면의 "초대 신청하기" 를 누릅니다 — Hangar 는 지금 초대받은 사람만 가입할 수 있고, 신청할 때 정한 이메일·비밀번호로, 관리자가 승인해 "가입 완료" 메일이 오면 바로 로그인합니다(초대 링크를 받았다면 그 링크로 가입). 로그인 후 워크스페이스가 없으면 하나 만듭니다. 이 방법은 자동화 키가 필요 없습니다 — 로그인 자체가 연결의 열쇠입니다.
2. AI 앱에 커넥터 주소 추가
커넥터 주소는 https://mcp.apps.2tl.io/mcp 입니다. 메뉴 이름은 앱 버전에 따라 조금 다를 수 있어요.
Claude (웹 · 데스크톱)
설정 → Connectors → Add custom connector 에서 이름은 아무렇게나(예: Hangar), 주소는 위 커넥터 주소를 넣습니다.
무료 플랜은 커스텀 커넥터를 1개까지 추가할 수 있습니다. Team·Enterprise 워크스페이스에서는 관리자가 먼저 추가해야 구성원이 쓸 수 있습니다. 모바일 앱은 이미 추가된 커넥터를 쓸 수는 있지만, 직접 추가할 수는 없습니다.
ChatGPT
설정 → 커넥터(개발자 모드) 에서 같은 주소를 추가합니다. 웹에서 Plus·Pro·Business·Enterprise·Edu 플랜이 쓸 수 있고, Free 플랜은 아직 지원하지 않습니다. Business 이상은 워크스페이스 소유자가 개발자 모드 커넥터를 허용해야 합니다.
이 주소는 브라우저로 로그인해서 쓰는 커넥터입니다. 키를 만들거나 설정 파일에 붙여 넣을 필요가 없습니다.
3. 로그인 · 허용 화면에서 허용
AI 가 Hangar 도구를 처음 부르면 로그인 화면이 뜹니다. 콘솔 계정으로 로그인하면 "AI 도구 연결" 화면(연결할 워크스페이스, 요청 권한, 연결 유지 기간 7·30·90일)이 나옵니다. 확인하고 허용을 누릅니다.
- 연결을 시작한 바로 그 브라우저에서 허용해야 합니다. 링크만 다른 곳(메일·메신저 등)으로 옮겨서 열면 거부됩니다 — 문제가 생기면 참고.
- 그 워크스페이스의 owner 만 허용할 수 있습니다. owner 가 아니면 로그인해도 허용 버튼이 없습니다.
- production 을 활성화하는 권한은 이 화면에 아예 없습니다 — AI 는 무엇을 허용하든 production 을 직접 켜지 못합니다.
4. 첫 프롬프트 보내기
대화창에 아래를 그대로 보내 연결을 확인합니다.
Hangar 연결이 잘 됐는지 확인해줘. hangar_whoami 를 불러서 결과를 알려줘.
연결이 확인되면 그대로 가져가는 파일의 "새 앱 만들기" 프롬프트로 첫 사이트를 만들어 봅니다.
AI 가 올리는 파일은 HTML·CSS·JS·SVG·JSON 같은 텍스트뿐입니다(한 번에 최대 20개·4MB, 프로젝트 전체 128개·25MB). npm 빌드도 하지 않습니다 — 그대로 동작하는 코드만 올라갑니다. 사진이 많거나 npm 빌드가 필요한 프로젝트는 아래 Claude Desktop 을 씁니다. 큰 이미지는 나중에 콘솔이나 CLI 로 따로 올립니다.
2Claude Desktop 에 연결하기
내 컴퓨터에 설치해서 쓰는 방법입니다. 여러 파일·이미지가 많은 프로젝트, npm 빌드가 필요한 프로젝트에는 이쪽이 낫습니다. 대괄호 < > 부분은 내 값으로 바꿉니다.
1. 콘솔 가입 · 워크스페이스
console.apps.2tl.io 를 엽니다. 계정이 없으면 로그인 화면의 "초대 신청하기" 를 누릅니다 — Hangar 는 지금 초대받은 사람만 가입할 수 있고, 신청할 때 정한 이메일·비밀번호로, 관리자가 승인해 "가입 완료" 메일이 오면 바로 로그인합니다(초대 링크를 받았다면 그 링크로 가입). 로그인 후 워크스페이스가 없으면 하나 만듭니다. 이미 웹에서 연결하기로 가입했다면 이 단계는 건너뜁니다.
2. 자동화 키 발급 (워크스페이스 owner)
자동화 키는 AI 가 내 워크스페이스에서 일할 때 쓰는 출입증입니다. 워크스페이스 → 키 화면(/w/{워크스페이스 ID}/keys) → 자동화 키 탭에서 발급합니다.
- 이름(예:
내 노트북 Claude), 만료(7·30·90일, 처음이면 30일)를 정합니다. - "production 활성화 허용"과 "잡이 있는 릴리스 활성화 허용"은 끈 채로 둡니다. 이래야 AI 가 실수로 공개 사이트를 바꾸지 못합니다.
- 발급을 누르면
hgw_로 시작하는 값이 한 번만 보입니다. 바로 복사해 안전한 곳(비밀번호 관리자 등)에 저장합니다. 워크스페이스 ID(콘솔 주소의/w/뒤 값)도 적어 둡니다.
AI 채팅창·메일·메신저·스크린샷에 붙여 넣지 않습니다. 다음 단계의 설정 파일에만 넣습니다.
3. Node.js 설치
nodejs.org 에서 LTS 버튼을 눌러 Node.js 22 이상을 설치합니다. Claude Desktop 이 Hangar 를 실행하는 데 필요합니다.
4. Claude Desktop 설정
전용 폴더를 하나 만듭니다(예: ~/HangarProjects). Claude Desktop 은 이 폴더가 어디인지 스스로 알지 못하므로, AI 가 만들 앱은 모두 이 폴더 안에 있어야 합니다.
- Claude Desktop 설정 → 개발자(Developer) → 설정 편집(Edit Config) 을 누르면
claude_desktop_config.json이 열립니다. (macOS~/Library/Application Support/Claude/claude_desktop_config.json, Windows%APPDATA%\Claude\claude_desktop_config.json) - 아래를 붙여넣고
<워크스페이스 ID>·<hgw_…>·허용 폴더 경로를 내 값으로 바꿉니다. Windows 는"/Users/나/HangarProjects"자리에"C:\\Users\\나\\HangarProjects"를 씁니다.
{
"mcpServers": {
"hangar": {
"command": "npx",
"args": ["-y", "@2tl/hangar-mcp@0.1.0"],
"env": {
"HANGAR_WORKSPACE_ID": "<워크스페이스 ID>",
"HANGAR_WORKSPACE_KEY": "<hgw_…>",
"HANGAR_ALLOWED_ROOTS": "/Users/나/HangarProjects"
}
}
}
}- 파일을 저장하고 Claude Desktop 을 완전히 종료했다가 다시 엽니다.
- 설정 → Extensions(확장) 에서 Filesystem 확장을 켜고, 같은 폴더(
~/HangarProjects)로 접근을 제한합니다. AI 가 실제로 파일을 쓰려면 필요합니다.
항상 @2tl/hangar-mcp@0.1.0 처럼 버전을 적습니다. 업데이트는 버전 숫자를 올려서 직접 합니다. npx hangar·npx appctl 은 Hangar 가 아닌 다른 사람의 패키지입니다.
동기화되거나 남과 공유하는 폴더 밖에 두세요. 키 만료는 최대 90일이고, 유출됐다고 생각되면 콘솔에서 바로 회수하고 새로 발급합니다.
5. 첫 프롬프트 보내기
Claude Desktop 대화창에 아래를 그대로 보내 연결을 확인합니다.
Hangar 연결이 잘 됐는지 확인해줘. hangar_whoami 를 불러서 결과를 알려줘.
연결이 확인되면 그대로 가져가는 파일의 "새 앱 만들기" 프롬프트로 첫 사이트를 만들어 봅니다.
3그대로 가져가는 파일
아래 세 가지를 그대로 복사해 두면, AI 가 매번 "preview 먼저, production 은 사람에게" 순서를 지킵니다. 웹 연결(ChatGPT·Claude 웹)과 Claude Desktop 모두에 씁니다.
| 파일 | 두는 곳 | 쓰는 도구 |
|---|---|---|
| Claude 프로젝트 지침 | Claude(웹·데스크톱) → Projects → 이 프로젝트의 project instructions | Claude (웹 연결 · Desktop) |
| SKILL.md | ~/.claude/skills/hangar/SKILL.md | Claude Code |
| prompts.md | 아무 곳. 필요할 때 복사 | 모든 도구 |
Claude 프로젝트 지침
Claude 의 Projects 기능을 쓴다면(웹 연결이든 Desktop 이든), 프로젝트를 만들고 project instructions 에 아래를 붙여 넣습니다. 매번 규칙을 다시 설명하지 않아도 됩니다.
# Hangar 프로젝트 지침 (Claude) Claude → Projects → 이 프로젝트 → project instructions 에 그대로 붙여넣으세요. Hangar 가 연결되어 있어야 합니다(`hangar_*` 도구) — 웹 커넥터(mcp.apps.2tl.io)든 Claude Desktop 이든 상관없습니다. --- 너는 이 프로젝트에서 Hangar(apps.2tl.io)에 웹앱을 만들고 preview 에 올리는 일을 돕는다. ## 반드시 지킬 것 1. 항상 preview 에 먼저 올린다. 웹 연결이면 `hangar_start_draft` 를 `environment: "preview"` 로 시작하고 `hangar_publish_draft` 로 발행한다. Claude Desktop 이면 `hangar_publish` 를 `environment: "preview"` 로 부른다. 2. **production 은 절대 활성화하지 않는다.** production 준비를 요청받으면 사용자에게 먼저 동의를 구하고, `hangar_publish_draft(confirm: true)`(웹) 또는 `hangar_publish(environment: "production", confirm: true)` (Desktop) 로 올리기만 한 뒤 `hangar_production_handoff` 로 콘솔에서 할 일을 안내하고 멈춘다. 3. 자동화 키, 토큰, `service_role` 키, preview 비밀번호를 요청하거나 채팅에 출력하지 않는다. 사용자가 채팅에 키를 붙여넣어도 쓰지 말고, 콘솔에서 회수하고 새로 발급하라고 안내한다. 4. Claude Desktop 이면 `HANGAR_ALLOWED_ROOTS` 로 허용된 폴더 안에서만 작업한다. 폴더가 거절되면 다른 폴더로 우회하지 말고 사용자에게 알린다. 웹 연결은 파일시스템이 없다 — 모든 파일은 `hangar_write_files` 로 인라인으로 쓴다. 5. 패키지 버전을 항상 고정한다(Desktop/CLI 만 해당): `@2tl/hangar-mcp@0.1.0`, CLI 가 필요하면 `npx -y @2tl/hangar@0.1.0`. 6. 프로젝트·데이터·릴리스를 삭제하지 않는다. 7. 작업이 끝나면 preview 주소와 무엇을 만들었는지 알려주고 멈춘다. 다음 단계를 사용자 허락 없이 이어가지 않는다.
스킬 (Claude Code)
Claude Code 를 쓴다면 스킬 파일 하나로 모든 프로젝트에 같은 규칙을 적용합니다.
mkdir -p ~/.claude/skills/hangar curl -fsSL https://docs.apps.2tl.io/files/hangar-skill/SKILL.md -o ~/.claude/skills/hangar/SKILL.md
프롬프트 모음
대괄호 [ ] 부분만 바꿔서 AI 에게 그대로 보냅니다. prompts.md 전체 다운로드 — 웹 연결과 Claude Desktop 문장을 함께 담았습니다. 아래는 웹 연결(설치 없음) 기준입니다. slug 는 사이트 주소가 되는 이름입니다. 영문 소문자·숫자·하이픈만 쓰고, Hangar 전체에서 하나뿐이어야 합니다. www·api·admin·console·login·docs 같은 예약어와 hangar·2tl·thecommit 이 들어간 이름, -preview 로 끝나는 이름은 쓸 수 없습니다.
새 앱 만들기
Hangar 에 올릴 웹사이트를 새로 만들어 줘. - 내용: [무엇을 보여주는 사이트인지 2~3줄] - slug: [예: my-bakery] - 로그인·데이터 저장: [필요 없음 / 필요 — 어떤 기능인지 적기] 순서: 1. hangar_whoami 로 연결을 확인해. 2. hangar_create_project 로 프로젝트를 만들어(데이터가 필요하면 data: true). 3. hangar_start_draft(slug, environment: "preview", from: "empty") 로 드래프트를 시작해. 4. hangar_write_files 로 HTML/CSS/JS 를 드래프트에 써. 빌드 도구 없이 그대로 동작하는 코드로 만들어. 서버 코드는 쓰지 마. 5. 데이터가 필요하면 hangar_get_data_keys 로 preview 접속 정보를 받고, migrations/ 안의 SQL 파일로 테이블을 만들면서 같은 파일에서 RLS 도 켜. 화면 코드에는 받은 apiUrl·anonKey 를 그대로 적어 넣어. 6. hangar_list_draft 로 점검하고 거부되거나 빠진 파일이 있으면 고쳐. 7. hangar_publish_draft 로 발행하고 hangar_activate_preview 로 켜. 8. preview 주소를 알려주고 멈춰. production 은 건드리지 마.
Claude Desktop 이면: 3~7번 대신 이 폴더에 파일을 만들고 npm run build 로 빌드한 뒤 hangar_check_project → hangar_publish(preview) → hangar_activate_preview 를 씁니다.
수정하기
Hangar 프로젝트 [slug] 를 고쳐 줘. - 바꿀 것: [무엇을 어떻게] 순서: 1. hangar_release_status 로 지금 preview 와 production 에 무엇이 올라가 있는지 먼저 알려줘. 2. hangar_start_draft(slug, environment: "preview", from: "active") 로 지금 서빙 중인 내용에서 드래프트를 시작해. 3. hangar_write_files 로 바뀐 파일만 고쳐 써. 이미 있는 migrations 파일은 고치지 말고, 바꿀 게 있으면 다음 번호 파일을 새로 추가해. 4. hangar_list_draft → hangar_publish_draft → hangar_activate_preview. 5. preview 주소와 바뀐 점을 알려주고 멈춰.
데이터·로그인 추가
[slug] 에 회원가입·로그인과 데이터 저장 기능을 추가해 줘. - 기능: [예: 가입한 사람이 자기 글만 저장하고 볼 수 있음] 규칙: - hangar_create_project 를 같은 slug 로, data: true 로 다시 불러 데이터베이스를 켜. - 접속 정보는 hangar_get_data_keys 로 preview 환경 것만 받아. service_role 키는 쓰지도, 나에게 달라고도 하지 마. - 테이블은 migrations/ 안의 새 SQL 파일로만 만들고, 같은 파일에서 RLS 를 켜고 정책을 만들어. - 화면에서는 supabase-js 로 불러(anonKey 는 코드에 그대로 적어). - hangar_start_draft(from: "active") → hangar_write_files → hangar_list_draft → hangar_publish_draft → hangar_activate_preview. - preview 주소와 테스트 방법(가입해 볼 순서)을 알려주고 멈춰.
preview 보여줘
지금까지 만든 변경을 Hangar preview 에 올려 줘. hangar_start_draft(from: "active") 로 시작하고 hangar_write_files 로 바뀐 파일을 써. hangar_list_draft 로 점검한 뒤 hangar_publish_draft, hangar_activate_preview 순서로 해. 끝나면 preview 주소와 릴리스 ID 를 알려 줘. production 은 건드리지 마.
production 준비
[slug] 를 production 에 공개할 준비를 해 줘. 1. 데이터베이스를 쓰는 사이트면 hangar_get_data_keys 로 production 접속 정보를 받아, 그 값으로 파일을 다시 써. 2. hangar_start_draft(slug, environment: "production", from: "active") 로 시작하고 hangar_write_files 로 올려. 3. 나에게 물어봐. 동의하면 hangar_publish_draft(confirm: true) 로 올려. 활성화는 하지 마. 4. hangar_production_handoff 를 불러서, 내가 콘솔에서 누를 곳을 그대로 알려 줘.
되돌리기
되돌리기(롤백)는 콘솔에서만 합니다. AI 에게는 되돌리는 도구가 없습니다. 릴리스 화면에서 전에 잘 되던 릴리스를 골라 되돌린 뒤, AI 에게 아래처럼 요청합니다.
[slug] 에서 무엇이 잘못됐는지 찾아서 고치고, preview 에 다시 올려 줘.
4확인하고 공개하기
preview 확인
- 주소:
https://<slug>-preview.apps.2tl.io - 브라우저가 아이디·비밀번호를 물으면 아이디는
preview, 비밀번호는 콘솔 프로젝트 개요의 "프리뷰 접속" 카드에 있습니다(owner 만 볼 수 있습니다). - preview 의 데이터(가입한 계정, 저장한 글)는 production 과 완전히 따로입니다. 마음껏 시험해도 공개 사이트에 남지 않습니다.
production 공개 (사람이 한 번)
AI 는 사용자가 동의한 뒤에만 production 용 릴리스를 올립니다 — 이 단계는 활성화가 아니라 업로드(발행)입니다. 공개 사이트를 실제로 바꾸는 활성화는 사람만, 콘솔에서 합니다. 웹 연결이든 Claude Desktop 이든 이 규칙은 같습니다 — 어떤 연결에도 production 활성화 권한 자체가 없습니다.
- AI 에게 "production 준비" 프롬프트를 보냅니다. AI 가 동의를 구한 뒤 올리고, 콘솔 링크와 릴리스 ID 를 알려 줍니다.
- 콘솔 프로젝트의 릴리스 화면(
/w/{워크스페이스 ID}/projects/{프로젝트 ID}/releases)을 엽니다. - production 환경에서 AI 가 알려 준 릴리스를 찾아 production 으로 전환합니다. 보통 1~2분 안에
https://<slug>.apps.2tl.io에 반영됩니다.
잘못되면 같은 릴리스 화면에서 전에 잘 되던 릴리스로 되돌립니다(롤백). 되돌리기 프롬프트를 참고합니다.
5문제가 생기면
| 증상 | 원인과 해결 |
|---|---|
| 허용 화면에 "이 브라우저에서 시작되지 않았습니다" 라고 떠요 | 연결을 시작한 것과 같은 브라우저에서 다시 눌러야 합니다. AI 앱에서 연결을 새로 시작하고, 뜨는 화면을 그 자리에서 바로 허용합니다 — 링크를 다른 창·기기로 옮기면 거부됩니다 |
| 로그인해도 허용 버튼이 안 보여요 | 그 워크스페이스의 owner 가 아닙니다. AI 도구 연결은 owner 만 허용할 수 있습니다. owner 에게 부탁하거나, 내가 owner 인 워크스페이스로 다시 로그인합니다 |
| 며칠 전엔 됐는데 갑자기 안 돼요 | 연결 유지 기간(7·30·90일)이 끝났습니다. AI 앱에서 커넥터를 다시 연결하고 허용 화면에서 새로 허용합니다 |
| 키가 만료됐대요 / 안 된대요 (Claude Desktop) | 콘솔 워크스페이스 → 키 → 자동화 키 탭에서 새로 발급하고, 설정 파일의 키 값을 바꾼 뒤 Claude Desktop 을 다시 시작합니다 |
| AI 가 production 을 못 올린다고 해요 | 정상입니다. 공개는 사람만 콘솔에서 합니다. "production 준비"를 요청하면 AI 가 올려 두고 누를 곳을 알려 줍니다 |
| 이미 있던 프로젝트를 못 찾는대요 | 이 연결(또는 자동화 키)이 만들었거나, 허용할 때 고른 프로젝트만 다룹니다. 웹 연결은 허용 화면에서, 자동화 키는 콘솔에서 그 프로젝트를 포함해 다시 만듭니다 |
| slug 가 이미 쓰이고 있대요 | 다른 이름을 고릅니다. -preview 로 끝나는 이름과 예약어(www·api·admin·console·login·docs 등), hangar·2tl·thecommit 이 들어간 이름은 쓸 수 없습니다 |
| 폴더를 못 연대요 / 파일을 못 쓴대요 (Claude Desktop) | HANGAR_ALLOWED_ROOTS 가 프로젝트 폴더의 절대 경로인지 확인합니다. Filesystem 확장이 같은 폴더로 설정돼 있는지도 확인합니다 |
npx 를 찾을 수 없대요 (Claude Desktop) | Node.js 가 설치되지 않았습니다. nodejs.org 에서 LTS 를 설치하고 Claude Desktop 을 다시 시작합니다 |
| preview 비밀번호를 모르겠어요 | 콘솔 → 프로젝트 → 개요의 "프리뷰 접속" 카드입니다(아이디 preview, owner 만 볼 수 있음). AI 는 이 비밀번호를 볼 수 없습니다 |
6터미널을 쓸 수 있다면: Claude Code
터미널이 낯설지 않다면 Claude Code 로도 같은 일을 할 수 있습니다. 프로젝트 폴더에서 한 번만 등록합니다.
cd 내앱 read -rs HANGAR_KEY claude mcp add --scope user hangar \ -e HANGAR_WORKSPACE_ID=<워크스페이스 ID> \ -e HANGAR_WORKSPACE_KEY="$HANGAR_KEY" \ -- npx -y @2tl/hangar-mcp@0.1.0 unset HANGAR_KEY
자세한 설정과 CLI 명령은 개발자 · 설치와 로그인에 있습니다.
CLI 와 API 로 직접 다루기
Hangar 는 빌드가 끝난 정적 파일(HTML·JS·CSS·이미지)을 서빙합니다. 서버 코드는 실행하지 않습니다. 서버가 하던 일은 프로젝트 전용 PostgreSQL 에 브라우저에서 바로 붙는 REST API, 로그인, 파일 저장소로 대신하며, 셋 다 supabase-js 로 씁니다. 주기적인 수집·정리 작업은 예약 잡으로 돌립니다.
migrations/*.sql 을 번호 순서로 적용합니다.preview(<slug>-preview.apps.2tl.io, Basic 인증)와 production(<slug>.apps.2tl.io, 공개, 커스텀 도메인 가능)은 데이터베이스·로그인 계정·파일·시크릿까지 모두 따로인 별개 사이트입니다.
01설치와 로그인
Node 22 이상이 필요합니다.
npm i -g @2tl/hangar@0.1.0
hangar --help
# 설치 없이 (버전 고정, "npx hangar" 는 다른 패키지)
npx -y @2tl/hangar@0.1.0 whoami버전은 항상 고정합니다. 업데이트는 버전을 올려서 직접 합니다 / update by bumping the version deliberately. MCP 서버를 쓸 때는 AI 도구를 프로젝트 폴더 안에서 실행하거나 HANGAR_ALLOWED_ROOTS 를 설정해야 합니다.
API 주소는 기본값이 https://app-api.2tl.io 라 따로 적지 않아도 됩니다. 로그인 방법은 둘입니다.
자동화 키 (워크스페이스 단위)
콘솔 키 화면(/w/{workspaceId}/keys)의 자동화 키 탭에서 owner 가 발급합니다(만료 7·30·90일, 프로젝트 한도). "production 활성화 허용"과 "잡이 있는 릴리스 활성화 허용"은 끈 채로 둡니다. 값(hgw_)은 한 번만 보이므로 복사해서 바로 로그인합니다. 클립보드에서 --token-stdin 으로 넘기면 셸 히스토리에도 파일에도 남지 않습니다.
# macOS (Linux: xclip -selection clipboard -o | …, Windows PowerShell: Get-Clipboard | …) pbpaste | hangar login --workspace-key --workspace <workspace-uuid> --token-stdin hangar whoami # 키 이름, 만료, 만든 프로젝트 수, production 허용 여부
production 허용 키라도 AI 는 production 을 활성화하지 않습니다. production 전환은 사람이 콘솔 릴리스 화면에서 합니다.
키 하나가 그 키로 만든 프로젝트(와 발급 때 고른 프로젝트) 전부와 두 환경을 다룹니다.
프로젝트 토큰 (프로젝트·환경 단위)
키 화면에서 프로젝트·환경·범위(scope)·만료를 골라 발급합니다.
| 하려는 일 | scope | 발급할 수 있는 역할 |
|---|---|---|
조회, data keys, jobs list | project:read | owner, developer |
publish | release:publish | owner, developer |
activate · rollback · pause | release:activate | owner |
jobs run · secrets set/unset | release:activate | owner |
# 콘솔에서 복사한 토큰을 클립보드에서 바로 넘깁니다 pbpaste | hangar login --workspace <workspace-uuid> --project-id <project-uuid> \ --environment preview --token-stdin hangar whoami --json # 여러 프로젝트·환경은 프로필로 pbpaste | hangar login --profile myapp-prod --workspace <workspace-uuid> --project-id <project-uuid> \ --environment production --token-stdin hangar profiles # 저장된 로그인 목록 hangar use myapp-prod # 기본 프로필 바꾸기 hangar target --profile myapp-prod --json # 명령마다 지정도 가능
로그인 정보는 ~/.hangar/credentials.json(권한 0600)에 저장됩니다.
echo "hgw_…" > key.txt 는 셸 히스토리에 남고, 프로젝트 안 파일은 커밋되거나 발행될 수 있습니다. 토큰은 명령 인자나 환경변수가 아니라 --token-stdin 으로만, 클립보드에서 바로 넘깁니다. 파일로 잠시 들고 있어야 하면 프로젝트 밖에 나만 읽게 만들고 로그인 뒤 지웁니다: (umask 077; pbpaste > ~/hangar-key.txt) → hangar login … --token-stdin < ~/hangar-key.txt && rm ~/hangar-key.txt. 혹시 모르니 key.txt·token*.txt 는 .gitignore 에 넣어 둡니다.
프로젝트 만들기: hangar project create
자동화 키로 로그인한 상태에서 사이트 디렉터리에 대해 실행합니다. 데이터베이스가 필요 없으면 --data 를 뺍니다. 빌드는 하지 않으므로 처음에는 준비만 하고 멈춥니다. 빌드한 뒤 같은 명령을 다시 치면 발행하고 preview 에 활성화합니다.
hangar project create my-site --data --project ~/work/my-site # 프로젝트 생성 → 프로필 my-site → 데이터 켜기(preview·production) → 준비 대기 # → .env.local 에 preview 키 → hangar.json 생성 → 빌드 결과 없음, 여기서 멈춤 cd ~/work/my-site && npm run build hangar project create my-site --data --project ~/work/my-site # → 발행 → preview 활성화 → https://my-site-preview.apps.2tl.io
- 이미 된 단계는 건너뜁니다. 중간에 끊겨도 같은 명령을 다시 치면 이어갑니다.
- 이후 명령은
--profile my-site로 이 프로젝트를 가리킵니다. - slug 는 사이트 주소라 Hangar 전체에서 하나뿐입니다.
-preview로 끝나는 slug 와www·api·admin·console·status는 쓸 수 없습니다. - 키의 프로젝트 한도는 프로젝트를 지워도 줄지 않습니다. 다 쓰면 새 키를 발급합니다.
production 은 따로 합니다. anon 키가 빌드 때 박히므로 production 키로 다시 빌드해 발행하고, 활성화는 사람이 콘솔 릴리스 화면에서 합니다.
hangar data keys --profile my-site --environment production --write-env .env.production.local
npm run build
hangar publish --profile my-site --environment production --project . \
--idempotency-key $(uuidgen | tr A-Z a-z)
# 그다음 콘솔 /w/{workspaceId}/projects/{projectId}/releases 에서 이 릴리스로 전환service_role 키 읽기, 데이터 끄기·키 회전, preview 비밀번호 보기, 멤버·토큰·키 관리, 발급 때 고르지 않은 기존 프로젝트, jobs run·secrets set/unset. 잡이 있는 릴리스는 발행만 되고 활성화·롤백은 "잡이 있는 릴리스 활성화 허용" 키나 콘솔에서 합니다. 키가 새도 기존 데이터는 읽히지 않습니다.
02배포와 롤백
hangar.json
프로젝트 루트에 둡니다. outputDir 은 빌드 결과 폴더입니다. CLI 는 빌드를 대신 돌리지 않으므로 먼저 빌드합니다. hangar init 이 기본 파일을 만들어 줍니다.
{
"version": 1,
"name": "my-app",
"runtime": "static",
"outputDir": "dist",
"entrypoint": "index.html",
"spa": true,
"compatibility": { "profile": "supabase-postgrest-v1" }
}| 필드 | 설명 |
|---|---|
name | 소문자 slug, 63자 이하 |
outputDir | 발행할 폴더. 프로젝트 밖이나 심볼릭 링크는 안 됩니다 |
spa | true 면 /shelf/abc 같은 딥링크에 index.html 을 돌려줍니다. 클라이언트 라우팅을 쓰면 켭니다 |
compatibility.profile | 데이터베이스를 쓰면 supabase-postgrest-v1 |
requiredSecrets · jobs | 예약 잡을 쓸 때. 06 참고 |
점검, 발행, 활성화
hangar doctor --project ~/work/my-app --json # 스키마·경로·RLS 누락·잡 번들 점검 hangar inspect --project ~/work/my-app --json # 올라갈 파일 목록과 digest hangar publish --project ~/work/my-app \ --idempotency-key $(uuidgen | tr A-Z a-z) --json # → "id": 릴리스 id hangar target --json # → "revision": n hangar activate --release <release-id> --expected-revision <n> \ --idempotency-key $(uuidgen | tr A-Z a-z) --reason "첫 배포" --json
- idempotency-key: 같은 키로 다시 보내면 새 릴리스를 만들지 않고 첫 결과를 돌려줍니다. 네트워크 오류 재시도에 안전합니다.
- expected-revision: 그 사이 누가 먼저 전환했다면 거부됩니다.
target을 다시 읽고 재시도합니다. - 전환 시간: 보통 1~2분 안에 새 릴리스가 서빙됩니다. 진행은 콘솔 배포 탭에서 봅니다.
되돌리기와 멈추기
hangar releases --json hangar rollback --release <이전 release-id> --expected-revision <n> \ --idempotency-key $(uuidgen | tr A-Z a-z) --reason "이전 릴리스로 복귀" --json hangar pause --expected-revision <n> \ --idempotency-key $(uuidgen | tr A-Z a-z) --reason "점검" --json
롤백은 과거에 실제로 activate 했던 릴리스로만 됩니다. DB 스키마는 되돌아가지 않습니다. 그래서 migration 은 항상 이전 코드와도 호환되게(컬럼 추가 위주로) 씁니다.
03에셋과 CDN
릴리스의 파일은 두 갈래로 서빙됩니다. 개발자가 할 일은 상대 경로로 참조하는 것 하나입니다.
핵심 파일
.html .js .mjs .css .json .wasm
사이트 도메인에서 무결성을 검증한 뒤 no-store 로 내보냅니다. 새 릴리스가 즉시 반영됩니다.
에셋
이미지·영상·폰트 등 그 밖의 모든 파일
CDN 에서 서명된 URL 로 직접 나갑니다. HTML·CSS 안의 상대 경로는 서빙할 때 자동으로 CDN 주소로 바뀝니다.
- 에셋은
<img src="assets/logo.png">,url(assets/bg.jpg)처럼 상대 경로로 씁니다. 사이트 도메인이나 절대 URL 을 적으면 치환되지 않습니다. - 캐시 무효화용 쿼리스트링(
logo.png?v=3)을 붙이지 않습니다. 치환에서 빠지고 느린 경로로 갑니다. 릴리스마다 내용 해시로 서빙되므로 필요하지 않습니다. - JS 에서
fetch('assets/data.bin')처럼 상대 경로로 부르면 CDN 으로 리다이렉트됩니다. - 한도: 파일 128개, 전체 500MB. 핵심 파일은 개별 16MiB, 합계 64MiB.
.env,.npmrc,.pgpass같은 자격증명 파일이outputDir에 있으면 발행이 거부됩니다./_hangar/는 예약 경로입니다.
사용자가 올리는 파일(프로필 사진, 첨부)은 릴리스 에셋이 아니라 05 파일 저장소로 다룹니다.
04데이터베이스
프로젝트·환경마다 전용 PostgreSQL 이 하나씩 붙고, 그 앞에 REST(PostgREST 호환)와 로그인이 사이트 주소의 /api 아래에 뜹니다.
1. 켜기
콘솔 프로젝트의 데이터 탭에서 "데이터 활성화"를 누릅니다. 보통 1분 안에 ready 가 됩니다. preview 도 쓰려면 환경 토글을 바꿔 한 번 더 켭니다. 자동화 키가 있으면 hangar project create <slug> --data 가 두 환경을 함께 켜고 .env.local 까지 써 줍니다.
hangar data status --json # "status": "ready" 인지 확인 hangar data keys --write-env .env.local # HANGAR_API_URL, HANGAR_ANON_KEY 기록
2. 두 가지 키
| 키 | 받는 곳 | 쓰는 곳 |
|---|---|---|
anon | hangar data keys, 콘솔 | 브라우저 번들에 그대로 넣습니다. RLS 가 지켜 주는 공개 키입니다 |
service_role | 콘솔(owner 만) | RLS 를 우회합니다. 브라우저 코드·저장소·AI 채팅에 절대 넣지 않습니다. 로컬 배치 스크립트의 .env*.local 이나 예약 잡(자동 주입)에서만 씁니다 |
3. 브라우저에서 부르기
import { createClient } from '@supabase/supabase-js';
// 접미사 없는 base URL. /rest/v1, /auth/v1, /storage/v1 은 라이브러리가 붙입니다.
const supabase = createClient(import.meta.env.VITE_HANGAR_API_URL, import.meta.env.VITE_HANGAR_ANON_KEY);
await supabase.auth.signUp({ email, password });
await supabase.auth.signInWithPassword({ email, password });
const { data } = await supabase.from('todos').select('*').order('created_at');
await supabase.from('todos').insert({ title }); // user_id 는 DB default 가 채웁니다콘솔이 보여 주는 restUrl(…/api/rest/v1)을 createClient 에 넣으면 경로가 두 번 붙어 404 가 납니다. https://<slug>.apps.2tl.io/api 를 넣습니다. Vite 의 VITE_* 값은 빌드할 때 번들에 박히므로 환경을 바꾸면 다시 빌드합니다.
4. 스키마는 migration 으로만
프로젝트 루트의 migrations/(outputDir 밖)에 NNNN_lower-kebab.sql 로 둡니다. publish 가 함께 올리고, activate 뒤 새 코드가 서빙되기 직전에 번호 순서로 적용됩니다.
create table todos ( id uuid primary key default gen_random_uuid(), user_id uuid not null default auth.uid(), title text not null, done boolean not null default false, created_at timestamptz not null default now() ); alter table todos enable row level security; create policy "todos are owner-only" on todos using (auth.uid() = user_id) with check (auth.uid() = user_id); -- 누구나 읽는 공개 카탈로그라면 -- create policy "catalog is public" on catalog for select using (true);
- RLS 를 켜지 않은 테이블은 API 에서 아예 보이지 않습니다. 테이블을 만드는 같은 파일에서 RLS 와 정책을 함께 만듭니다.
hangar doctor가 누락을 경고합니다. - 적용된 파일은 고치지 않습니다. 내용이 바뀌면 배포가
MIGRATION_CHANGED로 멈추고 이전 릴리스가 계속 서빙됩니다. 바꾸려면 새 번호 파일을 추가합니다. auth.uid(),auth.role(),auth.jwt()는 플랫폼이 미리 만들어 둡니다.- 집계·정렬 로직은
security_invoker = true뷰로 만들면 호출자 권한으로 돌아 RLS 가 그대로 적용됩니다. service_role은 모든 RLS 테이블에 쓰기 권한을 가집니다. 배치 적재는 이 키로 합니다.
5. 시간대
프로젝트 DB 의 기본 시간대는 Asia/Seoul 입니다. timestamptz 컬럼은 REST 응답에 2026-09-27T15:08:39+09:00 처럼 나오고, current_date·date_trunc('day', …) 도 서울 자정 기준입니다. 플랫폼 API 자체의 JSON 은 UTC(…Z)로 나오며, 같은 순간이므로 new Date() 로 읽으면 맞습니다.
6. 로그인 메일
가입 확인·비밀번호 재설정 메일은 플랫폼이 보냅니다. 메일 속 링크를 누르면 확인을 마친 뒤 사이트로 돌아옵니다. 받는 쪽 스팸함도 확인합니다.
05파일 저장소
supabase-js 의 Storage API 와 같은 방식입니다. 파일은 CDN 에 저장되고, 메타데이터는 프로젝트 DB 의 storage.objects 에 들어갑니다.
insert into storage.buckets (id, name, public) values
('avatars', 'avatars', false), -- 올린 사람만 읽기
('covers', 'covers', true) -- 누구나 읽기, <img src> 에 바로 사용
on conflict (id) do nothing;const { data: { user } } = await supabase.auth.getUser(); // 업로드는 로그인 필요
await supabase.storage.from('avatars').upload(`${user.id}/me.png`, file, { upsert: true });
const { data: signed } = await supabase.storage.from('avatars').createSignedUrl(`${user.id}/me.png`, 600);
const { data: pub } = supabase.storage.from('covers').getPublicUrl('item-001.jpg');
// pub.publicUrl → https://<slug>.apps.2tl.io/api/storage/v1/object/public/covers/item-001.jpg
// 키 없이 열립니다. 요청하면 CDN 주소로 리다이렉트됩니다.서버 쪽 배치(수집한 이미지 저장 등)는 service_role 키로 같은 REST 경로에 올립니다.
curl -X POST "$HANGAR_API_URL/storage/v1/object/covers/item-001.jpg" \ -H "apikey: $HANGAR_SERVICE_ROLE_KEY" -H "Authorization: Bearer $HANGAR_SERVICE_ROLE_KEY" \ -H "Content-Type: image/jpeg" -H "x-upsert: true" --data-binary @cover.jpg
| 항목 | 동작 |
|---|---|
| 최대 크기 | 25 MiB, 넘으면 413. 0 byte 는 400 |
| 익명 업로드 | 불가. 로그인 사용자 또는 service_role |
| 서명 URL | 1~3600초 |
| 버킷 생성·삭제 | API 없음. migration 으로만 |
| 이미지 변환 | 미지원(transform) |
06예약 잡
수집·동기화 같은 배치 스크립트를 cron 으로 돌립니다. 잡은 활성화된 릴리스에 선언된 것만 돌고, 활성화할 때마다 그 릴리스의 잡 목록으로 바뀝니다. 실행 환경에는 Node 22 와 Playwright Chromium 이 들어 있습니다.
{
"requiredSecrets": [{ "name": "EXAMPLE_API_KEY", "consumer": "job" }],
"jobs": [
{
"name": "sync-data",
"schedule": "15 2 * * *",
"timeZone": "Asia/Seoul",
"entry": "jobs-dist/sync-data.mjs",
"size": "browser",
"timeoutSeconds": 3600,
"args": ["--allow-network"],
"secrets": ["EXAMPLE_API_KEY"]
}
]
}| 필드 | 규칙 |
|---|---|
schedule | 5필드 cron, 시간당 최대 6회 |
timeZone | 생략하면 Asia/Seoul |
entry | outputDir 밖의 단일 .mjs 번들, 16MB 이하. 안에 두면 공개 파일이 됩니다 |
size | small, 또는 브라우저 자동화가 필요하면 browser |
| 자동 주입 | HANGAR_API_URL, HANGAR_SERVICE_ROLE_KEY 와 secrets 에 적은 값 |
| 쓰기 가능 경로 | /tmp(os.tmpdir())만. 나머지는 읽기 전용입니다 |
| 네트워크 | 공개 인터넷의 80/443 포트와 자기 사이트로만 나갈 수 있습니다 |
| 할당량 | 환경당 하루 6시간(서울 자정 기준). 넘으면 경고합니다 |
번들은 프로젝트가 직접 만듭니다. playwright 와 Node 내장 모듈만 런타임이 제공하므로 나머지는 모두 번들에 넣습니다.
npx esbuild scripts/sync-data.ts --bundle --platform=node --target=node22 \ --format=esm --external:playwright --external:playwright-core \ --outfile=jobs-dist/sync-data.mjs # 시크릿: 값은 stdin 으로만, 출력되지 않습니다 (owner) hangar secrets set EXAMPLE_API_KEY < example-key.txt hangar secrets list hangar publish --project . --idempotency-key $(uuidgen | tr A-Z a-z) --json # activate 후 hangar jobs list # 다음 실행 시각(KST)·마지막 결과 hangar jobs run sync-data --reason "첫 실행" hangar jobs runs sync-data hangar jobs logs <run-id> # 로그 꼬리, 시크릿 값은 가려집니다
콘솔의 프로젝트 → 잡 탭에서도 같은 기록을 보고 지금 실행할 수 있습니다. 시크릿은 설정 → 시크릿 카드에서 관리합니다.
07REST API
CLI 가 하는 일은 플랫폼 API(https://app-api.2tl.io/api/v1)로도 할 수 있습니다. 전체 API 레퍼런스(OpenAPI)는 요청 시 제공합니다. 가능하면 CLI 를 권합니다.
| 인증 | 형태 | 쓰는 곳 |
|---|---|---|
| 프로젝트 토큰 | Authorization: Bearer <token> + X-Hangar-Environment: preview|production | 업로드·릴리스·활성화·데이터 상태·잡·시크릿 |
| 콘솔 로그인 | 브라우저 세션 | 워크스페이스·멤버·키 발급, 데이터 켜기, service_role 조회 |
API=https://app-api.2tl.io/api/v1 P="$API/workspaces/$WS/projects/$PROJECT" # 토큰 파일은 프로젝트 밖에 나만 읽게: (umask 077; pbpaste > ~/hangar-token.txt) H=(-H "Authorization: Bearer $(cat ~/hangar-token.txt)" -H "X-Hangar-Environment: preview") curl -s "${H[@]}" "$P/release-target" # 지금 서빙 중인 릴리스와 revision curl -s "${H[@]}" "$P/releases?limit=20" # 커서 페이지: { items, nextCursor } curl -s "${H[@]}" "$P/data" # 데이터 상태와 anon 키
- 환경 헤더: 프로젝트 토큰 요청에는
X-Hangar-Environment가 필요합니다. 토큰이 발급된 환경과 다르면 거부됩니다. - 멱등성: 발행·활성화·롤백·일시중지·잡 실행은 본문에 UUID
idempotencyKey를 받습니다. 같은 키로 재시도하면 첫 결과가 돌아오고, 같은 키에 다른 본문은 409 입니다. - 에러 모양:
{ "code", "message", "requestId", "details" }. 분기는code로 합니다.message는 바뀔 수 있습니다. 문의할 때는requestId를 함께 보냅니다. - 페이지: 목록은
limit와cursor를 받고{ items, nextCursor }를 돌려줍니다.nextCursor가 없으면 끝입니다. - 남의 자원: 다른 워크스페이스·프로젝트·환경의 자원은 403 이 아니라 404 로 답합니다.
프로젝트 데이터(REST·로그인·파일)는 플랫폼 API 가 아니라 각 사이트의 /api/rest/v1, /api/auth/v1, /api/storage/v1 로 부릅니다. 헤더는 apikey: <anon> 와 Authorization: Bearer <anon 또는 사용자 JWT> 두 개입니다.
curl -s "https://my-app.apps.2tl.io/api/rest/v1/todos?select=*&limit=5" \
-H "apikey: $ANON" -H "Authorization: Bearer $ANON" # 로그인 전: RLS 가 허용한 행만08문제 해결
| 증상 | 원인과 해결 |
|---|---|
hangar: command not found | npm i -g @2tl/hangar@0.1.0 로 설치하거나 npx -y @2tl/hangar@0.1.0 … 로 실행합니다. Node 22 이상인지 확인합니다 |
| activate 후 1분 정도 503 | 새 릴리스로 넘어가는 중입니다. 잠시 뒤 다시 확인합니다 |
| REST 가 403·401 | RLS 를 켜지 않았거나 정책이 없습니다. migration 에 enable row level security 와 create policy 를 넣습니다 |
조회가 항상 [] | 정책의 using 조건에 걸리지 않습니다. 로그인 상태와 user_id 기본값을 확인합니다 |
배포가 MIGRATION_CHANGED | 이미 적용된 migration 파일을 고쳤습니다. 되돌리고 새 번호 파일을 추가합니다 |
data keys 가 data_not_ready | 그 환경의 데이터를 아직 켜지 않았습니다. 콘솔 데이터 탭에서 켭니다 |
| 앱이 엉뚱한 환경 API 를 부름 | VITE_* 는 빌드 때 박힙니다. 대상 환경의 env 로 다시 빌드하고 번들에서 주소를 확인합니다 |
| 공개 버킷 이미지가 404 | 버킷이 public: false 입니다. 공개용 버킷을 따로 만들거나 서명 URL 을 씁니다 |
| 잡이 목록에 없음 | 잡을 선언한 릴리스를 아직 activate 하지 않았습니다. jobs list 의 releaseId 를 확인합니다 |
| 잡 로그에 모듈을 못 찾음 | 번들에 의존성이 빠졌습니다. doctor 의 job_bundle_not_self_contained 경고를 확인합니다 |
| production activate 가 404 | "production 활성화 허용"이 꺼진 자동화 키입니다. 정상 동작이며 콘솔 릴리스 화면에서 전환합니다 |
09콘솔이 필요한 단계
자동화 키를 쓰면 콘솔이 필요한 단계는 아래 넷입니다. 프로젝트 생성·데이터 켜기·preview 배포는 hangar project create 가 합니다.
| 단계 | 이유 |
|---|---|
| 워크스페이스 생성 | 권한 구조 자체라 콘솔에서 만듭니다 |
| 자동화 키 발급 | 키가 다른 키를 만들 수 없게 발급은 owner 전용입니다. 만료는 최대 90일입니다 |
service_role 키 조회 | RLS 를 우회하는 키라 owner 가 콘솔에서만 봅니다 |
| production 활성화 | 사용자에게 노출하는 결정이라 사람이 합니다 |
평소 흐름에는 없지만 역시 콘솔 전용인 작업: preview 비밀번호 조회·재설정, 데이터 끄기·키 회전, 멤버 관리, 자동화 키·프로젝트 토큰 회수.