
안녕하세요! 오늘은 Git 워크플로우의 품질과 보안을 한 단계 끌어올릴 수 있는 강력한 도구, 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가 필요한 이유를 구체적으로 살펴보겠습니다.
첫째, 보안 문제를 사전에 차단할 수 있습니다. 개발하다 보면 실수로 API 키, 비밀번호, 인증 토큰 등 민감한 정보를 커밋할 위험이 있습니다. husky를 통해 pre-commit 단계에서 이러한 민감 정보를 자동으로 감지하고 차단할 수 있습니다. 한 번 원격 저장소에 푸시된 비밀 정보는 Git 히스토리에 영구적으로 남기 때문에, 이를 사전에 방지하는 것이 매우 중요합니다.
둘째, 코드 품질을 일관되게 유지할 수 있습니다. ESLint, Prettier 같은 도구를 수동으로 실행하는 것은 번거롭고, 깜빡하기 쉽습니다. husky를 사용하면 커밋할 때마다 자동으로 린트 검사와 포맷팅이 실행되어, 코드베이스의 일관성을 자연스럽게 유지할 수 있습니다.
셋째, CI/CD 파이프라인의 부담을 줄여줍니다. 로컬에서 기본적인 검증을 마치고 푸시하면, 원격 CI에서 실패하는 경우가 줄어듭니다. 이는 빌드 시간과 리소스를 절약하고, 개발 속도를 높이는 데 기여합니다.
넷째, 커밋 메시지의 품질을 강제할 수 있습니다. Conventional Commits 같은 규칙을 적용하여 커밋 히스토리를 읽기 쉽게 만들고, 자동화된 체인지로그 생성이나 시맨틱 버저닝을 가능하게 합니다.
최신 husky v9 버전을 기준으로 설정하는 방법을 단계별로 살펴보겠습니다. 이 과정은 프로젝트 관리자가 최초 1회만 수행하면 됩니다.
먼저 프로젝트에 husky를 개발 의존성으로 설치합니다.
npm install --save-dev husky
프로젝트가 이미 Git 저장소로 초기화되어 있어야 합니다. 만약 Git이 초기화되지 않았다면 git init을 먼저 실행하세요.
husky를 초기화하면 필요한 디렉토리 구조와 기본 설정이 자동으로 생성됩니다.
npx husky init
이 명령어는 다음 작업들을 수행합니다:
.husky 디렉토리를 생성합니다pre-commit hook 예제 파일을 생성합니다package.json의 scripts에 "prepare": "husky" 스크립트를 추가합니다core.hooksPath를 .husky로 설정합니다prepare 스크립트는 다른 개발자가 npm install을 실행할 때 자동으로 husky를 설정하도록 보장합니다. 이것이 팀원 간 설정 공유의 핵심입니다.
이제 실제로 유용한 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
변경된 파일만 검사하여 속도를 높이려면 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"
]
}
}
민감 정보 유출 방지를 위해 추가적인 검사를 .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이 실패하면 커밋이나 푸시가 차단됩니다. 이때 당황하지 말고 다음 단계를 따르세요:
에러 메시지를 주의 깊게 읽으세요. 무엇이 문제인지 명확히 알려줍니다.
문제를 수정하세요. 린트 에러라면 코드를 수정하고, 커밋 메시지 에러라면 메시지를 수정합니다:
# 린트 에러 수정 후 다시 시도
git add .
git commit -m "feat: add user authentication"
# 커밋 메시지가 형식에 맞지 않으면
git commit --amend -m "feat: add user authentication module"
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로 업그레이드하는 경우:
#!/usr/bin/env sh 제거. "$(dirname -- "$0")/_/husky.sh" 제거 ~/.huskyrc 파일이 있다면 ~/.config/husky/init.sh로 이동npx 제거 (선택사항, 성능 개선).husky/_ 디렉토리는 자동 생성되며 .gitignore에 포함됨Husky v9는 이전 버전보다 훨씬 가볍고 빠르며, 사용법도 간단해졌습니다. 불필요한 래퍼 코드가 제거되어 hook 파일이 더 직관적이고 읽기 쉬워졌습니다. 단순한 도구를 넘어서 팀의 개발 문화를 개선하는 강력한 수단으로, 보안 문제를 사전에 차단하고, 코드 품질을 일관되게 유지하며, 불필요한 CI 실패를 줄여줍니다.
v9의 주요 개선사항을 요약하면:
처음에는 추가적인 제약으로 느껴질 수 있지만, 익숙해지면 자연스러운 개발 워크플로우의 일부가 됩니다. 중요한 것은 husky를 도입하는 목적이 팀원을 통제하는 것이 아니라, 모두가 더 나은 코드를 작성하도록 돕는 것이라는 점입니다. 명확한 규칙, 적절한 피드백, 그리고 지속적인 개선을 통해 husky는 여러분의 프로젝트를 한 단계 성숙시켜줄 것입니다.
Happy coding! 🪝✨