husky를 이용한 Git hook 사용하기🪝

Yoonmin·2025년 11월 4일

협업툴

목록 보기
2/2

안녕하세요! 오늘은 Git 워크플로우의 품질과 보안을 한 단계 끌어올릴 수 있는 강력한 도구, husky에 대해 이야기해보려고 합니다. 코드 리뷰에서 반복적으로 지적되는 문제들을 사전에 방지하고, 팀의 코딩 규칙을 자동으로 강제할 수 있다면 얼마나 좋을까요? husky가 바로 그 해답입니다.

Husky란 무엇인가?

Husky는 Git hook을 쉽게 관리하고 공유할 수 있게 해주는 npm 패키지입니다. Git hook이란 Git의 특정 이벤트(커밋, 푸시 등)가 발생했을 때 자동으로 실행되는 스크립트를 의미합니다. 예를 들어 코드를 커밋하기 직전에 자동으로 린트 검사를 실행하거나, 푸시하기 전에 테스트를 돌려볼 수 있습니다.

기본적으로 Git hook은 .git/hooks 디렉토리에 저장되는데, 이 디렉토리는 Git 저장소에 포함되지 않습니다. 즉, 각 개발자가 로컬 환경에서 개별적으로 설정해야 한다는 뜻이죠. 여기서 husky의 진가가 드러납니다. husky는 Git의 core.hooksPath 설정을 활용하여 hook 설정을 프로젝트에 포함시켜 모든 팀원이 동일한 규칙을 적용받을 수 있게 만들어줍니다.

최신 버전인 husky v9는 설정이 더욱 간소화되었고, 성능도 크게 개선되었습니다. 단 2KB의 매우 가벼운 크기로 zero-dependency로 동작하며, 약 1ms 만에 실행됩니다. 이전 버전과 비교해 불필요한 래퍼 코드가 제거되어 설치와 사용이 훨씬 직관적입니다.

왜 Husky를 사용해야 할까?

실제 개발 현장에서 husky가 필요한 이유를 구체적으로 살펴보겠습니다.

첫째, 보안 문제를 사전에 차단할 수 있습니다. 개발하다 보면 실수로 API 키, 비밀번호, 인증 토큰 등 민감한 정보를 커밋할 위험이 있습니다. husky를 통해 pre-commit 단계에서 이러한 민감 정보를 자동으로 감지하고 차단할 수 있습니다. 한 번 원격 저장소에 푸시된 비밀 정보는 Git 히스토리에 영구적으로 남기 때문에, 이를 사전에 방지하는 것이 매우 중요합니다.

둘째, 코드 품질을 일관되게 유지할 수 있습니다. ESLint, Prettier 같은 도구를 수동으로 실행하는 것은 번거롭고, 깜빡하기 쉽습니다. husky를 사용하면 커밋할 때마다 자동으로 린트 검사와 포맷팅이 실행되어, 코드베이스의 일관성을 자연스럽게 유지할 수 있습니다.

셋째, CI/CD 파이프라인의 부담을 줄여줍니다. 로컬에서 기본적인 검증을 마치고 푸시하면, 원격 CI에서 실패하는 경우가 줄어듭니다. 이는 빌드 시간과 리소스를 절약하고, 개발 속도를 높이는 데 기여합니다.

넷째, 커밋 메시지의 품질을 강제할 수 있습니다. Conventional Commits 같은 규칙을 적용하여 커밋 히스토리를 읽기 쉽게 만들고, 자동화된 체인지로그 생성이나 시맨틱 버저닝을 가능하게 합니다.

Husky 세팅하기

최신 husky v9 버전을 기준으로 설정하는 방법을 단계별로 살펴보겠습니다. 이 과정은 프로젝트 관리자가 최초 1회만 수행하면 됩니다.

1단계: Husky 설치

먼저 프로젝트에 husky를 개발 의존성으로 설치합니다.

npm install --save-dev husky

프로젝트가 이미 Git 저장소로 초기화되어 있어야 합니다. 만약 Git이 초기화되지 않았다면 git init을 먼저 실행하세요.

2단계: Husky 초기화

husky를 초기화하면 필요한 디렉토리 구조와 기본 설정이 자동으로 생성됩니다.

npx husky init

이 명령어는 다음 작업들을 수행합니다:

  • .husky 디렉토리를 생성합니다
  • 기본 pre-commit hook 예제 파일을 생성합니다
  • package.jsonscripts"prepare": "husky" 스크립트를 추가합니다
  • Git의 core.hooksPath.husky로 설정합니다

prepare 스크립트는 다른 개발자가 npm install을 실행할 때 자동으로 husky를 설정하도록 보장합니다. 이것이 팀원 간 설정 공유의 핵심입니다.

3단계: 실용적인 Hook 설정

이제 실제로 유용한 hook들을 설정해보겠습니다. 보안과 코드 품질을 모두 고려한 실무 중심의 설정을 구성하겠습니다.

Pre-commit Hook 설정

npx husky init을 실행하면 .husky/pre-commit 파일이 생성됩니다. v9에서는 더 이상 shebang이나 husky.sh를 참조할 필요가 없습니다. 파일을 다음과 같이 수정하세요:

# 린트 검사 (v9에서는 npx 없이 직접 실행 가능 - 약 0.2초 더 빠름)
npm run lint

# 타입 체크 (TypeScript 프로젝트인 경우)
npm run type-check

# 변경된 파일만 검사하려면 lint-staged 사용
npx lint-staged

중요: Husky v9에서는 #!/usr/bin/env sh. "$(dirname -- "$0")/_/husky.sh" 같은 코드가 deprecated되었습니다. 이런 코드는 제거해야 합니다. 파일에 실행할 명령어만 직접 작성하면 됩니다.

Commit-msg Hook 설정

커밋 메시지의 형식을 검증하는 hook을 추가하려면 직접 파일을 생성하거나 echo 명령어를 사용합니다. v9에서는 npx husky add 명령어가 제거되었으므로 다음 방법을 사용하세요:

# 방법 1: echo 명령어 사용 (권장)
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

# 방법 2: 직접 파일 생성
# .husky/commit-msg 파일을 생성하고 다음 내용을 입력

.husky/commit-msg 파일 내용:

npx --no -- commitlint --edit $1

이를 위해 commitlint도 설치해야 합니다:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

프로젝트 루트에 commitlint.config.js 파일을 생성합니다:

// commitlint.config.js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // 커스텀 규칙 추가 가능
    'type-enum': [
      2,
      'always',
      [
        'feat',     // 새로운 기능
        'fix',      // 버그 수정
        'docs',     // 문서 변경
        'style',    // 코드 포맷팅
        'refactor', // 리팩토링
        'perf',     // 성능 개선
        'test',     // 테스트 추가/수정
        'chore',    // 빌드 프로세스, 도구 변경
        'security'  // 보안 관련 변경
      ]
    ],
    'subject-case': [2, 'never', ['upper-case']]
  }
};

Pre-push Hook 설정

푸시하기 전에 테스트를 실행하는 것도 좋은 습관입니다:

echo "npm test" > .husky/pre-push

4단계: lint-staged 설정

변경된 파일만 검사하여 속도를 높이려면 lint-staged를 사용합니다:

npm install --save-dev lint-staged

package.json에 설정을 추가합니다:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml}": [
      "prettier --write"
    ]
  }
}

5단계: 보안 강화 설정

민감 정보 유출 방지를 위해 추가적인 검사를 .husky/pre-commit에 추가합니다:

# 변경된 파일만 검사 (lint-staged 사용)
npx lint-staged

# 환경 변수 파일이 커밋되지 않도록 검사
if git diff --cached --name-only | grep -qE "^\.env$|^\.env\."; then
  echo "❌ Error: .env files should not be committed!"
  echo "Please remove .env files from staging area."
  exit 1
fi

# 민감한 정보 패턴 검사 (선택적)
if git diff --cached | grep -qE "(password|secret|api[_-]?key|private[_-]?key|token)\s*[:=]"; then
  echo "⚠️  Warning: Possible sensitive information detected!"
  echo "Please review your changes carefully."
  exit 1
fi

추가 보안 도구 (선택사항):

더 강력한 보안 검사를 원한다면 gitleaks를 사용할 수 있습니다:

npm install --save-dev @gitleaks/gitleaks

.husky/pre-commit에 추가:

npx gitleaks protect --staged --verbose

관리자 입장에서의 사용 방법

프로젝트 관리자로서 husky를 도입하고 관리할 때는 몇 가지 중요한 원칙을 따라야 합니다.

명확한 문서화

팀원들이 husky의 목적과 동작 방식을 이해할 수 있도록 README.md에 명확히 문서화하세요. 특히 hook이 실패했을 때 어떻게 대처해야 하는지, 정당한 이유로 hook을 우회해야 할 때는 어떻게 하는지 설명해야 합니다.

## Git Hooks (Husky v9)

이 프로젝트는 코드 품질과 보안을 위해 Git hook을 사용합니다.

### 설정된 Hook들

- **pre-commit**: 린트, 포맷팅, 타입 체크 실행 및 민감 정보 검사
- **commit-msg**: 커밋 메시지 형식 검증 (Conventional Commits)
- **pre-push**: 전체 테스트 스위트 실행

### Hook 우회하기 (긴급한 경우만)

정말 긴급한 상황에서만 다음 방법을 사용하세요:

```bash
# 단일 커밋에만 적용
git commit --no-verify -m "hotfix: critical security patch"

# 또는 환경 변수 사용
HUSKY=0 git commit -m "hotfix: critical security patch"

단, 이는 정말 필요한 경우에만 사용하고, 나중에 반드시 해당 커밋을 정리하세요.


**점진적 도입**

처음부터 너무 엄격한 규칙을 적용하면 팀원들의 반발을 살 수 있습니다. 먼저 경고만 표시하도록 설정하고, 팀이 익숙해지면 점진적으로 강제하는 방식을 권장합니다.

```bash
# 처음에는 경고만 표시
npm run lint || echo "⚠️  Lint warnings detected, but allowing commit for now"

# 익숙해지면 강제로 변경
npm run lint

CI/CD와의 통합

husky는 로컬 검증이지만, CI/CD 파이프라인에서도 동일한 검사를 수행해야 합니다. husky를 우회하고 푸시한 코드도 잡아낼 수 있도록 이중 안전장치를 마련하세요.

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      HUSKY: 0  # CI에서는 husky 비활성화
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check
      - run: npm test
      - run: npx gitleaks detect --no-git -v  # 보안 검사

모니터링과 개선

정기적으로 hook 실행 로그를 검토하고, 팀원들의 피드백을 받아 규칙을 조정하세요. 너무 많은 false positive가 발생하면 개발 속도가 저하되고, 팀원들이 hook을 우회하려는 유혹을 느낄 수 있습니다.

버전 관리 전략

package.json에서 husky의 버전을 고정하고, 업데이트할 때는 팀 전체에 공지하세요:

{
  "devDependencies": {
    "husky": "~9.1.0"  // 마이너 버전만 자동 업데이트
  }
}

Monorepo 환경 설정

Monorepo 구조에서는 루트가 아닌 하위 디렉토리에 husky를 설정해야 할 수 있습니다:

// 하위 프로젝트의 package.json
{
  "scripts": {
    "prepare": "cd .. && husky frontend/.husky"
  }
}

협업자 입장에서의 사용 방법

이제 일반 개발자가 husky가 설정된 프로젝트에서 작업할 때 알아야 할 내용을 살펴보겠습니다.

최초 설정

프로젝트를 클론한 후 의존성을 설치하면 husky가 자동으로 설정됩니다:

git clone <repository-url>
cd <project-directory>
npm install  # prepare 스크립트가 자동으로 husky를 설정합니다

만약 husky가 제대로 설정되지 않았다면 수동으로 실행할 수 있습니다:

npm run prepare

설정이 제대로 되었는지 확인하려면:

# Git hook 경로 확인
git config core.hooksPath
# 결과: .husky

# Hook 파일 확인
ls -la .husky/

일상적인 워크플로우

husky가 설정되면 평소처럼 Git을 사용하면 됩니다. 다만 커밋이나 푸시할 때 자동으로 검사가 실행된다는 점을 인지하세요:

# 파일 수정 후
git add .
git commit -m "feat: add user authentication"
# 이 시점에서 pre-commit hook이 실행되어 린트, 포맷팅 등을 검사합니다
# commit-msg hook이 커밋 메시지 형식을 검증합니다

git push
# 이 시점에서 pre-push hook이 실행되어 테스트를 실행합니다

Hook 실패 시 대응

Hook이 실패하면 커밋이나 푸시가 차단됩니다. 이때 당황하지 말고 다음 단계를 따르세요:

  1. 에러 메시지를 주의 깊게 읽으세요. 무엇이 문제인지 명확히 알려줍니다.

  2. 문제를 수정하세요. 린트 에러라면 코드를 수정하고, 커밋 메시지 에러라면 메시지를 수정합니다:

# 린트 에러 수정 후 다시 시도
git add .
git commit -m "feat: add user authentication"

# 커밋 메시지가 형식에 맞지 않으면
git commit --amend -m "feat: add user authentication module"
  1. 자동 수정을 활용하세요. 많은 린트 도구는 자동 수정 기능을 제공합니다:
npm run lint -- --fix
# 또는
npx eslint . --fix

긴급 상황 대처

정말 긴급한 상황에서 hook을 우회해야 할 때가 있을 수 있습니다. 하지만 이는 최후의 수단이어야 합니다:

# Hook을 우회하고 커밋 (권장하지 않음)
git commit --no-verify -m "hotfix: critical security patch"

# 또는 환경 변수 사용 (일시적)
HUSKY=0 git commit -m "hotfix: critical security patch"

# 장기간 비활성화 (rebase/merge 중)
# ~/.config/husky/init.sh 파일에 다음 추가:
# export HUSKY=0

우회한 경우 반드시 나중에 해당 커밋을 정리하고, 팀에 알려야 합니다.

로컬 개발 환경 최적화

Hook 실행 시간이 너무 길면 개발 경험이 저하됩니다. 다음 방법으로 최적화할 수 있습니다:

# ESLint 캐시 활용
npm run lint -- --cache

# 변경된 파일만 검사 (lint-staged 사용)
# 이미 설정되어 있다면 자동으로 적용됩니다

# 특정 환경에서만 hook 실행
# ~/.config/husky/init.sh 파일에 조건부 로직 추가 가능

커밋 메시지 작성 팁

Conventional Commits 형식을 따르면 hook이 통과하기 쉽습니다:

<타입>(<범위>): <제목>

<본문>

<푸터>

예시:

feat(auth): add JWT token validation

- Implement token expiration check
- Add refresh token logic
- Update authentication middleware

Closes #123

타입 종류:

  • feat: 새로운 기능
  • fix: 버그 수정
  • docs: 문서 변경
  • style: 코드 스타일 변경 (포맷팅, 세미콜론 등)
  • refactor: 리팩토링
  • perf: 성능 개선
  • test: 테스트 추가/수정
  • chore: 빌드, 설정 파일 변경
  • security: 보안 관련 변경

문제 해결

Hook이 예상대로 동작하지 않으면:

# Husky 설정 확인
ls -la .husky
git config core.hooksPath

# Hook 파일 권한 확인 (실행 권한 필요 없음 - v9부터)
# Windows에서 생성한 hook도 chmod 불필요

# Husky 재설정
rm -rf .husky
npm run prepare

# 디버그 모드로 실행
HUSKY=2 git commit -m "test"

v9 마이그레이션 체크리스트

기존 프로젝트를 v9로 업그레이드하는 경우:

  1. 모든 hook 파일에서 #!/usr/bin/env sh 제거
  2. 모든 hook 파일에서 . "$(dirname -- "$0")/_/husky.sh" 제거
  3. ~/.huskyrc 파일이 있다면 ~/.config/husky/init.sh로 이동
  4. hook 파일에서 불필요한 npx 제거 (선택사항, 성능 개선)
  5. .husky/_ 디렉토리는 자동 생성되며 .gitignore에 포함됨

마치며

Husky v9는 이전 버전보다 훨씬 가볍고 빠르며, 사용법도 간단해졌습니다. 불필요한 래퍼 코드가 제거되어 hook 파일이 더 직관적이고 읽기 쉬워졌습니다. 단순한 도구를 넘어서 팀의 개발 문화를 개선하는 강력한 수단으로, 보안 문제를 사전에 차단하고, 코드 품질을 일관되게 유지하며, 불필요한 CI 실패를 줄여줍니다.

v9의 주요 개선사항을 요약하면:

  • 2KB의 초경량 패키지 (MIT 라이선스 파일이 가장 큰 파일입니다!)
  • 약 1ms의 빠른 실행 속도
  • deprecated된 코드 제거로 더 깔끔한 hook 파일
  • Windows 권한 문제 해결
  • 더 나은 디버깅 지원 (HUSKY=2)

처음에는 추가적인 제약으로 느껴질 수 있지만, 익숙해지면 자연스러운 개발 워크플로우의 일부가 됩니다. 중요한 것은 husky를 도입하는 목적이 팀원을 통제하는 것이 아니라, 모두가 더 나은 코드를 작성하도록 돕는 것이라는 점입니다. 명확한 규칙, 적절한 피드백, 그리고 지속적인 개선을 통해 husky는 여러분의 프로젝트를 한 단계 성숙시켜줄 것입니다.

Happy coding! 🪝✨

profile
'같이의 가치를'

0개의 댓글