왜 게이트인가
prod 배포는 코드가 main에 들어오는 순간 발동한다. 그래서 무엇이, 언제 main에 닿느냐가 전부다. 새 세션이 이미 main을 잡고 있든, 동료가 푸시하든, 내가 깜빡하든, 사람의 규율로는 막을 수 없다. 그래서 사람이 아니라 코드가 강제하는 게이트를 세웠다. 깨진 코드는 사용자에게 닿기 전에 자동으로 멈춘다.
$
케이스 08 · 배포 게이트
새 세션이 main을 잡고 있어도, 동료가 코드를 올려도, 내가 깜빡해도, 깨진 코드가 사용자에게 닿기 전에 자동으로 멈추도록 만든 배포 게이트. 세 겹의 방어, 배포 뒤가 아니라 PR 시점에 도는 CI, 그리고 한 마디면 승격 전체를 돌리는 에이전트.
feature 워크트리
실험과 실패가 자유로운 유일한 공간. 항상 최신 release에서 분기.
release dev 서버
사람과 에이전트의 변경이 처음 합쳐지는 곳. 실제 화면으로 사람 QA — 통과 전엔 아무것도 안 넘어감 ⏸
main 운영 · 실사용자
release에서만 승격. 초록불이 아니면 머지 자체가 거부 — 규칙이 아니라 코드로 강제.
⌀ 운영 직행 경로 없음 — pre-push 훅 · prod-gate · PR-only 머지, 3중 잠금
$
왜 게이트인가
prod 배포는 코드가 main에 들어오는 순간 발동한다. 그래서 무엇이, 언제 main에 닿느냐가 전부다. 새 세션이 이미 main을 잡고 있든, 동료가 푸시하든, 내가 깜빡하든, 사람의 규율로는 막을 수 없다. 그래서 사람이 아니라 코드가 강제하는 게이트를 세웠다. 깨진 코드는 사용자에게 닿기 전에 자동으로 멈춘다.
코드가 prod까지 가는 길
Worktree
feat/* · 격리 작업
Release
dev 배포 · 사람이 눈으로 QA
main
프로덕션 · 실사용자
3겹 방어
1 · 로컬 pre-push 훅
내 노트북에서 main으로 git push 할 때, 훅이 커밋을 release와 대조한다. release에서 검증되지 않은 커밋이면 git이 푸시를 거부한다. 내 손에서 나는 사고를, 무엇이 떠나기도 전에 막는다.
2 · CI (깨끗한 머신)
PR이 열리면 CI가 깨끗한 리눅스 머신을 띄워 빌드와 타입체크를 처음부터 돌린다. 훅이 못 보는 사각, 즉 동료의 코드와 내가 깜빡한 것까지 전수 검사한다. "내 맥북에선 되는데"를 깨끗한 머신에서 재현한다.
3 · 브랜치 보호 (서버)
GitHub 자체가 main을 PR로만, 그것도 CI가 녹색일 때만 merge되게 한다. main 직접 push는 서버가 거부한다. 나를 포함한 모두에게. 이 규칙은 누가 기억하느냐에 기대지 않는다.
왜 PR인가 — 타이밍
CI 혼자선 못 막는다. 타이밍이 전부이기 때문이다. main에 직접 push하면 CI는 배포가 이미 나간 뒤에야 돌기 시작한다. 그건 게이트가 아니라 사후 부검이다. 변경을 PR로 돌리면 CI가 merge 전에 돌고, 녹색이 되기 전까지 merge 버튼은 잠겨 있다. PR이 검문 지점을 만들고, CI가 그 안에서 검사하고, 브랜치 보호가 "녹색 아니면 merge 없음"을 못 박는다.
한 마디면, 에이전트가 승격을 돌린다
git switch -c promote/<날짜> origin/main
현재 origin/main 기준으로 승격 브랜치를 만든다.
git cherry-pick <QA 통과 커밋>
release에서 QA를 통과한 커밋만 골라 담는다. 검증된 것으로만 승격 범위를 좁힌다.
git push -u origin promote/<날짜>
승격 브랜치를 업로드한다.
gh pr create --base main
PR을 자동으로 연다. 아무도 GitHub 웹을 열지 않는다.
gh pr checks --watch
CI의 빌드와 타입체크가 녹색이 될 때까지 기다린다.
gh pr merge --squash
녹색이면 squash로 main에 머지하고, 그러면 프로덕션이 갱신된다.
배포 + 보고
배포가 트리거되고, 에이전트가 빌드 상태를 폴링해 결과를 보고한다. 이 전체에서 사람이 누르는 클릭은 0개다.
게이트가 잡는 것, 안 잡는 것
무엇이 게이트인지는 에디터가 아니라 tsconfig와 스크립트가 정한다. 한번은 에디터가 미사용 변수 30여 개를 노란 줄로 띄웠지만 타입체크는 rc=0이었다. 게이트는 노이즈가 아니라, 그대로 배포될 뻔한 진짜 타입 에러 네 개를 잡았다.
| CI가 막는다 (merge 차단) | CI가 통과시킨다 |
|---|---|
| 진짜 타입 에러 (Property does not exist on 'unknown') | 미사용 import (noUnusedLocals 꺼져 있어 tsc 통과) |
| 깨진 JSX 네임스페이스 (React 19 마이그레이션 누락) | 에디터의 노란 줄, 힌트일 뿐인 것 |
| import 경로 오타·문법 에러로 빌드가 깨지는 것 | UX·디자인 버그, dev에서 사람이 QA할 몫 |
규율이 아니라 코드가 강제한다
이 가운데 어느 것도 "조심하자"는 다짐에 기대지 않는다. 로컬 훅과 서버 규칙이 강제하고, 둘 다 실제로 적용돼 있다. 바람이 아니라 적용본이다. 결과는 작지만 분명한 보장이다. 내가 한 문장을 말하면, 검증된 코드만 라이브로 나간다.
$
근거 · 깃·위키 인용