# [CI/CD] 수동 태그에서 자동 릴리즈까지 — Git Flow와 Auto Release

> 🚀 [**우리 auto-release.yml 바로 보러가기 →**](https://github.com/Finders-Official/BE/blob/develop/.github/workflows/auto-release.yml)
> 
> 이 글에서 설명하는 워크플로우의 전체 코드를 바로 확인할 수 있다!

> main에 머지만 하면 버전 태그부터 릴리즈 노트까지 알아서 생긴다

---

## 1\. 우리의 Git 전략: Git Flow (경량 버전)

Finders 프로젝트는 **Git Flow** 전략을 사용하고 있다. 다만 hotfix나 release 브랜치 없이, 조금 가볍게 운영한다.

```plaintext
main        ← 운영 서버 (prod) 배포 브랜치
develop     ← 개발 서버 (dev) 배포 브랜치
feature/*   ← 기능 개발 브랜치 (이슈 기반)
fix/*       ← 버그 수정 브랜치
```

![Git Flow 브랜치 전략](https://nvie.com/img/git-model@2x.png align="left")

*출처:* [*A successful Git branching model*](https://nvie.com/posts/a-successful-git-branching-model/) *— Vincent Driessen*

### 흐름

1. **이슈 생성** → 브랜치 생성 (`feat/signup-api-#14`)
    
2. 개발 완료 → **develop으로 PR** → CI 통과 + 코드 리뷰 → **머지**
    
3. develop 머지 → **Dev 서버 자동 배포** (프론트와 연동 테스트)
    
4. 테스트 완료 → **develop → main PR** → 승인 → **머지**
    
5. main 머지 → **Prod 서버 자동 배포** + **릴리즈 자동 생성** ← 여기!
    

---

## 2\. 처음에는 수동이었다

처음에는 릴리즈를 **전부 수동**으로 만들었다.

```bash
# 1. main으로 이동
git checkout main
git pull origin main

# 2. 태그 생성
git tag v0.7.0

# 3. 태그 푸시
git push origin v0.7.0
```

그러면 `.github/release.yml` 설정에 의해 GitHub Release가 생성되었다.

### 뭐가 문제였을까?

* **매번 수동**으로 해야 함 → 까먹기 쉬움
    
* 버전 번호를 **직접 정해야** 함 → "이번에 뭐였지... v0.7? v0.8?"
    
* 릴리즈 노트를 **직접 작성**해야 함 → 귀찮아서 대충 쓰게 됨
    
* main 머지 후 태그 푸시를 **깜빡하면** 릴리즈가 안 생김
    

결국 issue [#344](https://github.com/Finders-Official/BE/issues/344)를 만들었다:

> "main 머지만 하면 태그 + 릴리즈가 자동 생성되도록 워크플로우를 개선합니다."

---

## 3\. Auto Release 워크플로우 만들기

### 전체 흐름

```mermaid
graph TD
    A["main에 PR 머지"] --> B["auto-release.yml 실행"]
    B --> C{"PR 제목에 버전이 있나?"}
    C -->|"[RELEASE] v0.9.2 배포"| D["v0.9.2 사용"]
    C -->|"버전 없음"| E["PATCH 자동 증가<br/>v0.9.1 → v0.9.2"]
    D --> F["태그 생성 & 푸시"]
    E --> F
    F --> G["커밋 로그 기반<br/>릴리즈 노트 생성"]
    G --> H["GitHub Release 발행"]
```

### 핵심 코드

**1단계: 버전 결정**

```yaml
# PR 제목에서 버전 추출 시도
if [[ "$COMMIT_MSG" =~ v([0-9]+\.[0-9]+\.[0-9]+) ]]; then
  VERSION="v${BASH_REMATCH[1]}"
  # → PR 제목이 "[RELEASE] v0.9.2 배포" 이면 v0.9.2 사용
else
  # 버전 없으면 PATCH 자동 증가
  IFS='.' read -ra PARTS <<< "${LAST_TAG//v/}"
  PATCH=$((PARTS[2] + 1))
  VERSION="v${PARTS[0]}.${PARTS[1]}.${PATCH}"
  # → v0.9.1 이 마지막이면 v0.9.2 자동 생성
fi
```

**2단계: 릴리즈 노트 자동 생성**

커밋 메시지의 **prefix(feat, fix, docs...)**를 분석해서 자동 분류한다:

```yaml
# feat 커밋만 모아서 Features 섹션
FEATS=$(git log "${LAST_TAG}..HEAD" --pretty=format:"- %s (%h)" --grep="^feat")

# fix 커밋만 모아서 Bug Fixes 섹션
FIXES=$(git log "${LAST_TAG}..HEAD" --pretty=format:"- %s (%h)" --grep="^fix")

# docs, refactor, test도 각각 분류
```

**3단계: GitHub Release 생성**

```yaml
gh release create "$VERSION" \
  --title "Release ${VERSION}" \
  --notes-file RELEASE_NOTES.md \
  --latest
```

---

## 4\. 실제 릴리즈는 이렇게 생긴다

📎 **실제 릴리즈 목록**: [Finders BE Releases →](https://github.com/Finders-Official/BE/releases)

예를 들어 **Release v0.9.5**의 릴리즈 노트는 이렇게 자동 생성되었다:

```markdown
# Release v0.9.5

## Changes since v0.9.4

### ✨ Features
- feat: 전용 terraform-ci SA 추가 및 Compute SA 역할 분리 (#396)
- feat: 공지 api 수정

### 🐛 Bug Fixes
- fix: swagger 문서 수정
- fix: 위치 미 동의시 현상소 조회의 경우 distanceKm가 반환되지 않게 수정
- fix: 배포 스크립트 서버 경로 통일 (#391)

### 📚 Documentation
- docs: 아키텍처 문서 업데이트 (#385)

### ♻️ Refactoring
- refactor: outputs.tf를 단일 jsonencode deploy_config로 통합 (#393)
```

**사람이 한 일: PR 머지 버튼 클릭. 끝.**

---

## 5\. 두 가지 배포 시나리오

### 시나리오 A: 일반 배포 (PATCH 자동 증가)

```plaintext
develop → main PR
제목: "[FIX] 이미지 업로드 버그 수정 (#123)"

→ 마지막 태그 v0.9.4에서 자동으로 v0.9.5 생성
```

### 시나리오 B: 메이저/마이너 릴리즈 (버전 명시)

```plaintext
develop → main PR
제목: "[RELEASE] v1.0.0 배포"

→ PR 제목에서 v1.0.0 추출해서 그 버전으로 태그 생성
```

**포인트:** 특별한 릴리즈(메이저, 마이너 버전 업)일 때만 PR 제목에 버전을 적으면 되고, 일반적인 패치는 알아서 버전이 올라간다!

---

## 6\. 버전 관리 규칙 (Semantic Versioning)

```plaintext
v MAJOR . MINOR . PATCH
  ↓       ↓       ↓
  1   .   2   .   3
```

| 버전 | 올리는 시점 | 예시 |
| --- | --- | --- |
| **MAJOR** | 호환 안 되는 큰 변경 | `v1.0.0` → `v2.0.0` |
| **MINOR** | 새 기능 추가 | `v1.0.0` → `v1.1.0` |
| **PATCH** | 버그 수정 | `v1.0.0` → `v1.0.1` |

현재 Finders는 아직 `v0.x.x` 대로, 빠르게 기능을 추가하는 단계다. `v1.0.0`은 정식 서비스 출시 시점에 찍을 예정!

---

## 7\. Before vs After

|  | Before (수동) | After (자동) |
| --- | --- | --- |
| **태그 생성** | `git tag v0.x.x` 직접 입력 | PR 머지 시 자동 |
| **버전 결정** | "이번 버전 뭐였지?" | PATCH 자동 증가 |
| **릴리즈 노트** | 대충 적거나 안 적음 | 커밋 기반 자동 분류 |
| **GitHub Release** | 수동 생성 or 까먹음 | **100% 자동** |
| **실수 가능성** | 태그 안 찍음, 중복 태그 | 중복 체크 내장 |

```mermaid
graph LR
    subgraph Before["❌ Before — 수동"]
        B1["main 머지"] --> B2["git tag 직접"] --> B3["git push 태그"] --> B4["릴리즈 수동 작성"]
    end
    subgraph After["✅ After — 자동"]
        A1["main 머지"] --> A2["전부 자동! 🎉"]
    end
```

---

## 8\. 마치며

릴리즈 자동화는 "편해지자"가 아니라 **"실수를 없애자"**에 가깝다.

수동으로 하면:

* 까먹는다 (태그 안 찍는 건 일상)
    
* 대충 한다 (릴리즈 노트 "잡다한 수정" 한 줄)
    
* 틀린다 (중복 태그, 잘못된 버전)
    

자동으로 하면:

* **머지만 하면 끝** → 깜빡할 수가 없음
    
* **커밋 메시지가 곧 릴리즈 노트** → feat/fix 잘 쓰는 습관이 중요해짐
    
* **중복 체크 내장** → 같은 태그 두 번 생성 불가
    

결국 **좋은 커밋 메시지 컨벤션 + Auto Release = 자동으로 정리되는 프로젝트 히스토리**라는 걸 깨달았다. 커밋 메시지를 대충 쓰면 릴리즈 노트도 대충 나오니까, 팀원들의 커밋 습관도 자연스럽게 좋아졌다. 👍

---

### 📎 참고

* 🚀 [auto-release.yml 전체 코드 보기](https://github.com/Finders-Official/BE/blob/develop/.github/workflows/auto-release.yml)
    
* [Issue #344: main 머지 시 GitHub Release 자동 생성 워크플로우 추가](https://github.com/Finders-Official/BE/issues/344)
    
* [Semantic Versioning 공식 사이트](https://semver.org/lang/ko/)
    
* [GitHub Actions - gh release create 문서](https://cli.github.com/manual/gh_release_create)
