프롤로그 - "브랜치 정책이 뭐예요?"
첫 협업 프로젝트 첫날, 시니어가 물었습니다:
"우리 팀은 Git Flow 쓸 거야.
develop에서 브랜치 따서 작업해."
저: "Git Flow가 뭐예요?"
시니어: (한숨) "Git 안 써봤어?"
저: "썼는데요... 그냥 git add, git commit, git push만..."
그때 이해했다. Git을 "쓴다"와 "잘 쓴다"는 완전히 다른 세계라는 걸. 마치 자전거를 탈 줄 안다고 해서 도심 교통을 다 이해하는 게 아닌 것처럼, 저는 Git의 기본 명령어만 알고 있었을 뿐 팀 협업의 철학은 전혀 몰랐습니다.
왜 공부하게 되었나
다른 프로젝트에 참여했습니다. 스타트업이었죠.
CTO: "우리는 Trunk Based Development 해. main에 바로 푸시해."
저: "엥? 브랜치 안 따요? 위험하지 않아요?"
CTO: "테스트 자동화 되어있고, 하루에 100번 배포해. 브랜치 관리가 더 느려."
같은 Git인데 철학이 정반대. 첫 프로젝트에서는 브랜치를 5가지나 쓰더니, 두 번째 프로젝트는 브랜치가 하나뿐. 이게 다 같은 버전 관리 시스템을 쓰는 건데 접근법이 완전히 달랐습니다. 처음엔 혼란스러웠지만, 이게 제가 브랜치 전략을 깊이 공부하게 된 계기였습니다.
처음엔 뭐가 이해가 안 갔나
- Git Flow는 브랜치가 왜 이렇게 많아?
master,develop,feature,release,hotfix... 머리 아파- Trunk Based는
main만 쓴다고? 충돌 안 나? - "하루에 100번 배포"는 말이 돼?
- 작은 기능 하나 수정하는데 왜 2주나 걸려?
main에 직접 푸시하면 production이 터지는 거 아냐?
무엇보다 "우리 팀은 뭘 써야 하지?"라는 질문에 답을 찾을 수가 없었습니다. 구글 검색해도 "상황에 따라 다르다"는 애매한 답변만 잔뜩...
깨달음의 순간 - "비행기 vs 스쿠터"
시니어의 비유가 모든 걸 명확하게 만들어줬습니다:
Git Flow = 보잉 747 (대형 여객기):
- 조종사 2명, 승무원 10명, 승객 400명
- 이륙 전 체크리스트 200개
- 연료 확인, 기체 점검, 관제탑 허가...
- 안전제일, 하지만 출발까지 2시간
- 하지만 한 번 이륙하면 400명을 안전하게 운송
Trunk Based = 전동 스쿠터:
- 혼자 타고 바로 출발
- 충전만 되어 있으면 1분 안에 도로로
- 빠름, 하지만 사고 나면 바로 다침
- 헬멧(테스트 자동화) 필수!
- 목적지 바꾸기도 쉽고, 실수해도 영향 범위가 작음
"규모와 위험도가 다르구나! 결국 이거였다."
보잉 747은 느리지만 안전합니다. 400명의 생명을 책임지니까요. 반면 전동 스쿠터는 빠르지만 위험합니다. 하지만 혼자 타니까 실수해도 나만 다치죠. 이 비유가 와닿았다. Git 브랜치 전략은 기술 선택이 아니라 위험 관리 전략이었던 겁니다.
1. Git Flow: 안전제일 대기업 스타일
브랜치 구조
master (production)
↓
develop
↓
feature/login
feature/payment
↓
release/v1.0
↓
hotfix/critical-bug
5가지 브랜치 타입
-
master(ormain): 실제 배포 버전- 절대 직접 커밋 금지
- 태그만 달림 (
v1.0.0,v1.0.1) - production 환경과 100% 동일한 코드만 존재
- 이 브랜치에 있는 건 "이미 고객이 쓰고 있는 코드"
-
develop: 개발 메인 브랜치- 다음 배포 준비하는 곳
- 모든 기능이 여기로 합쳐짐
- QA 환경과 연결됨
- "다음 달에 출시할 기능들의 집합소"
-
feature/*: 기능 개발develop에서 분기- 완료 후
develop으로 merge - 개발자가 가장 많이 쓰는 브랜치
- 예:
feature/user-login,feature/payment-system
-
release/*: 배포 준비develop에서 분기- 버그 수정만 허용 (새 기능 추가 금지)
- 완료 후
master와develop양쪽에 merge - "출시 직전 최종 점검 단계"
-
hotfix/*: 긴급 버그 수정master에서 분기- 수정 후
master와develop양쪽에 merge - production에서 버그 발견 시 즉시 사용
- "응급실 같은 존재"
실제 플로우
제가 로그인 기능을 만든다면:
# 1. develop에서 feature 브랜치 생성
git checkout develop
git pull origin develop
git checkout -b feature/login
# 2. 작업 (1주일)
git commit -m "Add login UI"
git commit -m "Add authentication logic"
git commit -m "Add error handling"
git commit -m "Add unit tests"
# 3. develop에 merge
git checkout develop
git merge feature/login
git push origin develop
git branch -d feature/login
# 4. 배포 준비 (QA 단계)
git checkout -b release/v1.0 develop
# QA가 버그 찾으면 여기서 수정
git commit -m "Fix login button color"
git commit -m "Fix validation message"
# 5. 배포
git checkout master
git merge release/v1.0
git tag v1.0.0
git push origin master --tags
# 6. develop에도 반영 (QA에서 수정한 버그픽스 포함)
git checkout develop
git merge release/v1.0
git push origin develop
소요 시간: 2주~1개월
이 과정을 처음 봤을 때 "너무 복잡한 거 아냐?"라고 생각했습니다. 하지만 금융권 프로젝트에 투입되고 나서야 이해했습니다. 한 번의 실수가 수천 명의 금전적 피해로 이어질 수 있는 환경에서는 이 정도 검증 단계가 필요하더군요.
장점
-
안정성: 여러 단계 검증
- feature 브랜치에서 개발자 자체 테스트
- develop에서 통합 테스트
- release 브랜치에서 QA 테스트
- master는 "검증된 코드만"
-
동시 개발: 10명이 동시에 다른 기능 개발 가능
- A는 로그인, B는 결제, C는 알림...
- 서로 충돌 없이 독립적으로 작업
-
버전 관리:
v1.0,v2.0명확히 구분- "v1.5에서 버그 발견했어요" → 즉시 해당 태그로 이동
- 여러 버전 동시 유지보수 가능
-
Hotfix 용이: production 버그 즉시 수정
- develop 브랜치 신경 안 쓰고 master에서 바로 분기
- 수정 후 양쪽에 merge하면 끝
단점
-
복잡함: 신입이 이해하기 어려움
- 저도 처음엔 "지금 어느 브랜치야?"를 하루에 10번 물어봄
- 실수로 master에 직접 커밋하면 팀 전체가 패닉
-
느림: 기능 하나에 2주 이상 소요
- 간단한 버튼 색 변경도 release 주기 기다려야 함
- "급한데..."라는 말이 통하지 않음
-
Merge Hell: 브랜치 많으면 충돌 지옥
- feature 브랜치 2주 작업 후 develop에 merge → 충돌 100개
- 충돌 해결하다가 하루 날림
-
과도한 절차: 작은 버그도 2주 기다림
- 오타 하나 고치는 데 release 주기 기다림
- "이 정도면 hotfix로...?" → "아니, 절차 지켜"
누가 쓸까?
- 패키지 소프트웨어: 1년에 4번 배포 (분기별)
- 금융권: 안전 최우선, 실수하면 대형 사고
- 대기업 SI 프로젝트: 수십 명이 동시에 작업
- 레거시 시스템: 변경 영향도가 큰 시스템
- 제조업 임베디드: 한 번 출하하면 수정 불가
제가 정리해본다면, Git Flow는 "절대 실수하면 안 되는 환경"을 위한 전략입니다.
2. Trunk Based Development: 속도제일 스타트업
브랜치 구조
main (= trunk)
├─ short-lived branch (1일 이하)
├─ short-lived branch
└─ short-lived branch
핵심: 브랜치 최소화, 빠른 merge
동작 방식
# 1. main에서 직접 작업 또는 짧은 브랜치
git checkout main
git pull
git checkout -b fix-button # 옵션
# 2. 작업 (몇 시간)
git commit -m "Fix button color"
git push origin fix-button
# 3. main에 즉시 merge (또는 PR 승인 후 즉시)
git checkout main
git merge fix-button
git push
git branch -d fix-button
# 또는 main에 직접 push (더 과감한 팀)
git checkout main
git add .
git commit -m "Fix button"
git push
소요 시간: 몇 시간~1일
처음 이 방식을 봤을 때 "위험하지 않나?"라고 생각했습니다. main에 직접 푸시? production이 터지는 거 아냐? 하지만 CTO가 보여준 대시보드를 보고 입이 떡 벌어졌죠. 하루에 정말 100번 배포하고 있었습니다. 그것도 에러율 0.1% 이하로.
핵심 원칙
-
작은 커밋: 큰 기능도 매일 작은 조각으로 쪼개서 merge
- "결제 시스템 전체"를 한 번에 merge ❌
- "결제 UI만", "검증 로직만", "API 연동만"... 하루에 하나씩 ✅
- 덩어리가 작으니 충돌도 적고, 문제 생기면 롤백도 쉬움
-
Feature Flag: 미완성 기능도 merge (단, 숨김 처리)
- "다 만들고 merge"가 아니라 "만들면서 계속 merge"
- 코드는 main에 있지만 사용자는 못 봄
-
빠른 피드백: CI/CD가 자동으로 테스트
- 커밋하면 30초 안에 테스트 결과 나옴
- 실패하면 즉시 롤백
Feature Flag 예시
// 미완성 기능도 main에 merge 가능
const PaymentPage = () => {
const { featureFlags } = useFeatureFlags();
if (featureFlags.newPayment) {
return <NewPaymentUI />; // 개발 중 (내부 테스터만 봄)
} else {
return <OldPaymentUI />; // 현재 사용 (일반 사용자가 봄)
}
};
// 환경변수로 제어
// .env.production
FEATURE_NEW_PAYMENT=false // 일반 사용자는 false
// .env.development
FEATURE_NEW_PAYMENT=true // 개발자는 true
배포 시 featureFlags.newPayment = false로 숨김. 완성되면 true로 바꾸기만 하면 됩니다. 심지어 점진적 롤아웃도 가능합니다:
// 10%의 사용자에게만 새 기능 보여주기
if (featureFlags.newPayment && user.id % 10 === 0) {
return <NewPaymentUI />;
}
이 패턴을 받아들였을 때, 세상이 달라 보였습니다. "기능 완성 = 코드 merge"라는 고정관념이 깨지더군요.
장점
-
속도: 하루에 100번 배포 가능
- 아침에 고친 버그가 오후에 사용자한테 전달됨
- 실험 → 검증 → 수정 사이클이 몇 시간 단위
-
단순함: 브랜치 하나뿐
- "어느 브랜치야?" 고민 불필요
- Git 명령어 5개만 알면 됨
-
충돌 최소화: 자주 merge → 큰 충돌 없음
- 2주 동안 분리된 브랜치 → merge 시 충돌 100개
- 매일 merge → 충돌 0~2개
-
진짜 CI: Continuous Integration의 본래 의미
- CI는 "Continuous"인데 2주에 한 번 merge하면 그게 CI인가?
- Trunk Based가 진짜 "지속적" 통합
단점
-
위험함: 버그가 바로 production
- 테스트 빼먹으면 사용자가 바로 경험
- "실수했네" → "사이트 터졌네"까지 5분
-
테스트 필수: 자동화 없으면 폭망
- 100% 테스트 커버리지 필수는 아니지만
- 핵심 기능은 반드시 자동화 테스트
-
문화 필요: 팀 전체가 동의해야 함
- 한 명이라도 "난 테스트 안 써"하면 팀 전체 위험
- 상호 신뢰와 책임감 기반
-
실력 필요: 큰 기능을 작게 쪼개는 능력
- "결제 시스템"을 10개의 작은 조각으로 쪼개기
- 각 조각이 독립적으로 동작하도록 설계
- 주니어에게는 어려운 스킬
누가 쓸까?
- 구글, 페이스북, 넷플릭스: 하루에 수천 번 배포
- 스타트업: 빠른 실험, 빠른 수정 (pivot 가능성)
- SaaS 서비스: 언제든 배포 가능한 환경
- CI/CD가 완벽한 팀: 테스트 자동화 100%
3. 경험 - Git Flow → Trunk Based 전환
제가 다녔던 스타트업에서 실제로 전환을 경험했습니다.
Before (Git Flow)
기능 개발: 1주
Code Review: 2일
QA: 1주
배포 대기: 1주 (다음 release 주기까지)
총 3주 후 사용자 피드백
문제점:
- 3주 후 "이 기능 필요 없대요" → 3주 날림
- 경쟁사가 먼저 출시 (우리보다 2주 빨랐음)
- QA에서 발견한 버그 수정도 다음 release까지 대기
- 개발자: "이미 완성했는데 왜 배포 못 해요?"
실제로 있었던 일입니다. 제가 만든 "소셜 로그인" 기능을 3주 동안 개발했는데, 막상 사용자 테스트를 해보니 "그냥 이메일 로그인이 더 편해요"라는 피드백. 3주가 증발했습니다.
After (Trunk Based + Feature Flag)
Day 1: 기본 UI merge (Feature Flag로 숨김 처리)
Day 2: 로직 추가 merge (내부 테스터에게만 공개)
Day 3: 테스트 통과, 10% 사용자에게 공개
Day 4: 사용자 피드백 수집 (실시간 모니터링)
Day 5: 피드백 반영, 100% 공개 또는 롤백
효과:
- 3주 → 5일 (6배 빨라짐)
- 빠른 실험, 빠른 폐기 (실패해도 5일만 날림)
- 경쟁사보다 빠른 출시
- 사용자 피드백 기반 개발 (추측이 아닌 데이터)
같은 "소셜 로그인" 기능을 Trunk Based로 다시 만들었을 때, Day 3에 사용자 피드백을 받았고 "버튼 위치가 이상해요"라는 걸 바로 파악해서 Day 4에 수정했습니다. Git Flow였으면 3주 후에야 알았을 것입니다.
4. CI/CD의 중요성
Trunk Based의 필수 조건
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Run unit tests
run: npm test
- name: Run integration tests
run: npm run test:integration
- name: Check code coverage
run: npm run coverage
# 테스트 실패 시 merge 불가
- name: Block if failed
if: failure()
run: |
echo "Tests failed! Cannot merge."
exit 1
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."
# AWS, Vercel, Netlify 등 배포 스크립트
규칙: 테스트 실패 시 merge 금지! 예외 없음!
제가 겪은 사고
한 번 테스트를 건너뛰고 main에 push한 적이 있습니다:
git push --no-verify # ❌ 위험! pre-commit hook 무시
이유: "급해서... 테스트는 로컬에서 돌렸는데 다 통과했어..."
결과:
- Production 다운 20분
- 사용자 불만 트윗 30개
- CTO한테 호출당함
- 팀 회의에서 사과
- 1주일 동안 배포 권한 박탈
진짜 원인: 로컬에서는 통과했지만 production 환경 변수가 달랐음. CI/CD에서는 실패했을 텐데 건너뛰었던 거죠.
교훈:
- 자동화 없는 Trunk Based = 자살 행위
- "급해서"는 변명이 안 됨
--no-verify는 팀원들에 대한 배신
이 사건 이후로 팀에서 규칙을 만들었습니다: "--no-verify 쓰면 커피 한 턴". 농담처럼 들리지만 진짜로 시행했고, 덕분에 다시는 이런 사고가 없었습니다.
5. 팀 규모별 추천
| 팀 규모 | 추천 전략 | 이유 |
|---|---|---|
| 1~3명 | Trunk Based | 브랜치 관리 오버헤드 불필요. 서로 뭐 하는지 다 알고 있음 |
| 4~10명 | Trunk Based + PR | 코드 리뷰는 하되 빠르게 merge (1일 이내) |
| 10~30명 | GitHub Flow | main + feature 브랜치, 단순화된 Git Flow |
| 30명 이상 | Git Flow | 여러 팀 동시 작업, 버전 관리 필수 |
| 금융/의료 | Git Flow | 안전 최우선. 한 번 실수하면 법적 문제 |
6. Hybrid: GitHub Flow (중간 지점)
제가 요즘 쓰는 방식입니다:
main (항상 배포 가능 상태)
├─ feature/login (PR 1~3일)
├─ feature/payment (PR 1~3일)
└─ hotfix/bug (Direct merge 또는 즉시 PR)
규칙:
- 기능 개발: 짧은 브랜치 + PR (1~3일 안에 merge)
- PR 승인 즉시 merge: "나중에"는 없음
main= 항상 배포 가능: 언제든 배포 버튼 누를 수 있는 상태- 자동 배포: merge 즉시 production (또는 staging → production)
실제 워크플로우:
# 1. 기능 브랜치 생성 (월요일 오전)
git checkout -b feature/dark-mode
# 2. 작업하면서 계속 커밋
git commit -m "Add dark mode toggle button"
git commit -m "Add dark mode styles"
# 3. PR 생성 (화요일 오전)
git push origin feature/dark-mode
# GitHub에서 PR 생성
# 4. 코드 리뷰 (화요일 오후)
# 동료: "LGTM! 👍"
# 5. Merge (화요일 오후)
# Squash and merge 또는 Merge commit
# 6. 자동 배포 (화요일 저녁)
# CI/CD가 자동으로 production에 배포
장점:
- Git Flow보다 단순 (브랜치 2~3개)
- Trunk Based보다 안전 (코드 리뷰 필수)
- 대부분의 팀에 적합 (현실적 선택)
- 배우기 쉬움 (신입도 1주일이면 적응)
7. 실수 모음
실수 1 - Git Flow로 스타트업 하기
CEO: "경쟁사가 어제 출시했는데 우리는?"
개발자: "QA 중입니다. 다음 주 release 브랜치에서..."
CEO: "다음 주? 지금 당장 필요한데?"
개발자: "절차가..."
CEO: "😡"
실제로 제가 첫 스타트업에서 겪은 일입니다. 경쟁사가 "라이브 채팅" 기능을 출시했는데, 우리는 이미 만들어놨지만 release 주기 때문에 2주를 기다려야 했죠. 결국 고객 10명을 경쟁사에 뺏겼습니다.
교훈: 스타트업에 Git Flow는 과함. 속도가 생명인 환경에서는 Trunk Based 또는 GitHub Flow.
실수 2 - 테스트 없이 Trunk Based
# 금요일 저녁 6시
git add .
git commit -m "Quick fix"
git push # 테스트 없이 main에 push
# 금요일 저녁 6시 5분
"사이트 안 돼요!"
"결제가 안 되는데요?"
"로그인 버튼이 사라졌어요!"
제 동료가 겪은 일입니다. 금요일 저녁에 "작은 수정"이라고 테스트 없이 푸시했다가... 주말 내내 출근해서 롤백하고 수정했죠.
교훈: 자동화 없으면 Git Flow가 안전. "빠른 배포"와 "무책임한 배포"는 다름.
실수 3 - 긴 Feature 브랜치 (Trunk Based 실패 사례)
Week 1: feature/big-refactor 시작
Week 2: 계속 작업 (main은 30개 커밋 앞서감)
Week 3: 드디어 완성!
Week 3: Merge 시도 → 충돌 200개
Week 3: 충돌 해결하다가 2일 소요
Week 3: 다시 테스트 → 실패 50개
Week 4: 포기하고 다시 작성
제가 직접 겪은 지옥입니다. "큰 리팩토링"을 한 번에 하려다가 3주 동안 브랜치를 따로 관리했고, merge 할 때 폭망했습니다.
교훈:
- 브랜치는 짧게 (1~3일)
- 큰 작업도 작은 조각으로 쪼개기
- "나중에 한 번에 merge"는 거짓말
올바른 방법:
Day 1: 폴더 구조 변경 (merge)
Day 2: import 경로 수정 (merge)
Day 3: 함수명 변경 (merge)
Day 4: 로직 최적화 (merge)
8. 정리 - 선택 기준
Git Flow를 써야 할 때
✅ 배포 주기가 길다 (월 1회 이상 간격) ✅ 여러 버전 동시 유지 (v1 지원 + v2 개발) ✅ 대규모 팀 (30명 이상) ✅ 안전이 최우선 (금융, 의료, 인프라) ✅ 패키지 소프트웨어 (고객이 직접 설치) ✅ 레거시 시스템 (변경 영향도 큼)
Trunk Based를 써야 할 때
✅ 하루에 여러 번 배포 ✅ 빠른 실험, 빠른 수정 (A/B 테스트 많이 함) ✅ CI/CD 자동화 완벽 (테스트 커버리지 80% 이상) ✅ 작은 팀 (10명 이하) ✅ SaaS 서비스 (언제든 업데이트 가능) ✅ 팀 문화가 성숙함 (상호 신뢰, 책임감)
GitHub Flow를 써야 할 때
✅ 위 둘의 중간 (대부분의 팀)
✅ 코드 리뷰 문화 (PR 필수)
✅ 하루일주일 단위 배포
✅ 스타트업중견 기업 (10~30명)
✅ 속도와 안정성 둘 다 중요
✅ 현실적 선택 (완벽한 자동화 어려움)
마치며 - "도구가 아닌 철학"
Git Flow vs Trunk Based는 기술의 차이가 아닌 철학의 차이입니다.
- Git Flow: "실수하지 말자. 검증하고 또 검증하자."
- Trunk Based: "빨리 실수하고, 빨리 고치자."
- GitHub Flow: "적당히 조심하면서, 적당히 빠르게."
회사 초기 (Git Flow): "왜 이렇게 느려? 답답해..." 지금 (GitHub Flow): "딱 적당한 속도네."
정답은 없습니다. 우리 팀에 맞는 걸 선택하면 됩니다.
제가 여러 회사를 거치며 배운 교훈:
- 팀 규모가 전략을 결정함: 3명 팀과 30명 팀은 다른 전략 필요
- 안전 vs 속도의 트레이드오프: 둘 다 완벽할 순 없음
- 자동화가 속도를 만듦: CI/CD 없으면 느릴 수밖에 없음
- 문화가 기술보다 중요: 좋은 전략도 문화 없으면 실패
다만 한 가지는 확실합니다:
"브랜치 전략 없이 Git 쓰는 건 운전면허 없이 운전하는 것과 같다."
첫 프로젝트에서 "Git Flow가 뭐예요?"라고 물었던 제가 부끄럽지만, 그 경험 덕분에 지금은 팀 상황에 맞는 브랜치 전략을 설계할 수 있게 되었습니다. 여러분도 우리 팀에 맞는 전략을 찾아가시길 바랍니다.