프로젝트가 오래될수록 의존성은 조용히 낡아간다. 보안 취약점 알림을 받고 나서야 여러 버전을 한꺼번에 올리려다 빌드가 무너지는 경험은 대부분의 팀이 겪는다. 이 “한 번에 몰아서 업데이트”의 고통을 없애는 방법은 단순하다. 작게, 자주, 자동으로 올리는 것이다. 그 일을 사람 대신 해주는 대표적인 도구가 Dependabot과 Renovate다.
두 도구는 모두 “낡은 의존성을 감지해 업데이트 PR을 자동으로 열어준다”는 같은 목적을 갖지만, 설정 철학과 유연성은 상당히 다르다. 이 글에서는 두 도구의 동작 모델, 실전 설정, 자동 병합(auto-merge) 구성, 그리고 자동화가 오히려 팀을 괴롭히는 실패 패턴까지 실제 설정과 함께 정리한다.
두 도구는 무엇이 다른가
Dependabot은 GitHub에 기본 내장되어 있다. 설정 파일 하나만 두면 별도 인프라 없이 동작하고, 보안 취약점 기반 업데이트(security updates)는 설정조차 필요 없다. 진입 장벽이 거의 없는 대신, 세밀한 제어에는 한계가 있다.
Renovate는 훨씬 강력하고 유연하다. 업데이트 그룹핑, 자동 병합 조건, 스케줄, 정규식 커스텀 매니저까지 거의 모든 것을 설정으로 표현할 수 있다. 대신 설정이 방대하고, GitHub 앱 또는 셀프호스팅으로 실행 주체를 갖춰야 한다.
- Dependabot: GitHub 내장, 제로 인프라, 보안 업데이트 자동, 설정이 단순한 대신 표현력 제한
- Renovate: 앱/셀프호스팅, 그룹핑·자동병합·스케줄·커스텀 매니저 등 고도의 제어, 학습 곡선 존재
판단 기준은 이렇다. 리포지토리가 몇 개 안 되고 “낡은 것 좀 알려줘” 수준이면 Dependabot으로 충분하다. 모노레포이거나 수십 개 리포에 정책을 일관되게 적용하고 병합까지 자동화하고 싶다면 Renovate가 답이다.
Dependabot: 최소 설정으로 시작하기
Dependabot은 .github/dependabot.yml 하나로 동작한다. 여러 패키지 에코시스템(package-ecosystem)을 나열하고 각각의 업데이트 주기를 지정하는 구조다. 아래는 npm과 GitHub Actions, Docker 베이스 이미지를 함께 관리하는 예시다.
# .github/dependabot.yml
version: 2
updates:
# 애플리케이션 npm 의존성
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly" # 매주 월요일 새벽에 스캔
day: "monday"
time: "04:00"
timezone: "Asia/Seoul"
open-pull-requests-limit: 5 # 동시에 열리는 PR 상한 (스팸 방지)
labels:
- "dependencies"
# 마이너·패치는 묶고, 메이저는 개별 PR 로 분리
groups:
minor-and-patch:
update-types:
- "minor"
- "patch"
# GitHub Actions 버전도 함께 관리 (자주 잊는 영역)
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Dockerfile 베이스 이미지
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
여기서 가장 중요한 설정은 open-pull-requests-limit과 groups다. 이 둘이 없으면 Dependabot이 수십 개의 PR을 한꺼번에 쏟아내 팀이 알림 피로에 빠진다. 마이너·패치를 묶으면 리뷰 부담이 줄고, 메이저만 개별 PR로 남겨 위험한 변경에 집중할 수 있다. 세밀한 제어가 필요하면 ignore로 특정 업데이트를 배제해, 감당할 수 없는 메이저 업그레이드를 잠시 고정할 수도 있다.
Renovate: 설정 상속과 프리셋
Renovate의 강점은 프리셋(preset) 상속이다. extends로 검증된 설정 묶음을 가져와 필요한 부분만 덮어쓴다. config:recommended는 합리적 기본값을, :dependencyDashboard는 대기 중 업데이트를 하나의 이슈로 모아 보여준다.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard",
":semanticCommits"
],
"timezone": "Asia/Seoul",
"schedule": ["after 9pm every weekday", "before 6am every weekday"],
"prConcurrentLimit": 10,
"prHourlyLimit": 3,
"labels": ["dependencies"],
"packageRules": [
{
"description": "개발 의존성의 마이너·패치는 하나로 묶어 자동 병합 대상",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "dev dependencies (non-major)"
},
{
"description": "메이저 업데이트는 개별 PR + 안정화 기간 부여",
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "7 days"
}
]
}
minimumReleaseAge는 특히 유용하다. 새 버전 직후에는 회귀 버그로 곧 다음 패치가 나오는 일이 잦은데, 이 옵션은 릴리스 후 일정 기간이 지난 버전만 채택해 갓 나온 불안정한 버전을 자동으로 끌어오는 위험을 줄인다.
schedule도 중요한 운영 장치다. 업데이트 PR을 늦은 밤이나 새벽으로 몰아두면 CI 경합을 피하고 아침에 정리된 상태로 리뷰를 시작할 수 있다.
업데이트를 그룹으로 묶기
PR 개수는 자동화 성패를 가르는 결정적 변수다. 100개의 의존성이 각각 PR을 열면 아무도 리뷰하지 않고 자동화가 통째로 무시된다. 함께 움직이는 패키지를 하나의 PR로 묶는 것이 핵심이다.
{
"packageRules": [
{
"description": "관련 패키지를 하나의 PR 로 묶기 (모노레포 스타일 배포)",
"matchPackagePatterns": ["^@company/", "^eslint"],
"groupName": "linting toolchain"
},
{
"description": "테스트 도구는 한 세트로 함께 이동",
"matchPackageNames": ["jest", "ts-jest", "@types/jest"],
"groupName": "jest"
}
]
}
같은 릴리스 주기를 공유하는 패키지 묶음(예: 프레임워크 코어와 플러그인들)은 함께 올려야 버전 불일치 런타임 오류를 피한다. Renovate는 이런 모노레포 프리셋을 내장해 유명 라이브러리 군을 자동으로 묶어주고, 커스텀이 필요하면 위처럼 패턴이나 이름으로 그룹을 정의한다.
자동 병합 파이프라인 구성
자동화의 궁극적 목표는 신뢰할 수 있는 업데이트를 사람 개입 없이 병합하는 것이다. 다만 무조건 병합하면 사고로 이어진다. 전제 조건은 CI 테스트 통과, 그리고 위험도가 낮은 업데이트(주로 패치)로의 한정, 두 가지다.
{
"packageRules": [
{
"description": "패치 업데이트는 CI 통과 시 자동 병합",
"matchUpdateTypes": ["patch", "pin", "digest"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
}
]
}
자동 병합이 안전하게 돌아가려면 브랜치 보호 규칙(branch protection)이 반드시 함께 있어야 한다. PR에 걸린 CI 워크플로(npm ci → lint → test → npm audit --audit-level=high)를 필수 상태 검사(required status checks)로 지정하면, 이를 통과하지 못한 PR은 병합되지 못한다. 자동 병합은 브랜치 보호를 우회하는 기능이 아니라, 그 규칙을 만족한 PR을 대신 눌러주는 기능일 뿐이다.
테스트가 부실하면 자동 병합은 위험을 확대하는 증폭기가 된다. 켜기 전에 “이 테스트가 통과했다면 배포해도 안전한가?”를 되물어야 한다.
보안 취약점 우선 처리
일반 버전 업데이트와 보안 패치는 긴급도가 다르다. 취약점이 공개된 의존성은 스케줄을 기다릴 것 없이 즉시 PR을 열고 우선 병합해야 한다. Dependabot은 이를 별도 기능(security updates)으로 제공하며 저장소 설정에서 활성화만 하면 된다. 조직 규모에서는 열린 보안 경보를 한눈에 집계해 현황을 파악하는 것이 중요하다.
# 조직 전체 저장소의 열린 보안 경보를 한눈에 집계
gh api graphql -f query='
query($org: String!) {
organization(login: $org) {
repositories(first: 100) {
nodes {
name
vulnerabilityAlerts(states: OPEN) { totalCount }
}
}
}
}' -f org=my-org --jq
'.data.organization.repositories.nodes[]
| select(.vulnerabilityAlerts.totalCount > 0)
| "(.name): (.vulnerabilityAlerts.totalCount)건"'
Renovate에서는 vulnerabilityAlerts 설정으로 보안 업데이트에만 다른 정책을 적용한다. 보안 PR은 schedule을 빈 배열로 두어 즉시 열고, labels로 우선순위를 부여하며, minimumReleaseAge를 해제해 안정화 대기 없이 채택한다. 일반 업데이트와 보안 업데이트를 같은 대기열에 섞지 않는 것이 핵심이다.
커스텀 매니저: Renovate가 모르는 파일 다루기
표준 패키지 매니저 파일이 아닌 곳에도 버전이 박혀 있는 경우가 많다. 셸 스크립트의 도구 버전, 인프라 코드의 이미지 태그 같은 것들이다. Renovate의 커스텀 정규식 매니저(regex manager)는 대상 파일에 아래처럼 주석 힌트를 달아 두면 임의의 파일에서도 버전을 인식해 업데이트한다.
# scripts/install-tools.sh
# renovate: datasource=github-releases depName=hashicorp/terraform
TERRAFORM_VERSION="1.9.5"
# renovate: datasource=github-releases depName=helm/helm
HELM_VERSION="3.15.4"
curl -fsSL "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" -o tf.zip
이 방식으로 Terraform 프로바이더, Helm 차트, Dockerfile의 다운로드 URL에 박힌 버전까지, 표준 매니저가 닿지 못하는 사각지대를 정규식으로 통합 관리할 수 있다.
자동화가 팀을 괴롭히는 흔한 실패 패턴
의존성 자동화는 잘못 다루면 도입 전보다 상황을 악화시킨다. 흔한 실패 패턴은 다음과 같다.
- PR 폭탄: 그룹핑과 동시 PR 상한이 없어 수십 개의 PR이 쏟아진다. 팀은 곧 모든 의존성 PR을 무시하고 자동화는 방치된다.
- 테스트 없는 자동 병합: 검증이 부실한 채
automerge를 켜면 회귀 버그가 조용히 메인 브랜치로 흘러든다. 자동 병합의 신뢰도는 테스트 신뢰도를 넘지 못한다. - 메이저를 마이너처럼 취급: 파괴적 변경이 담긴 메이저를 자동 병합에 포함하면 언젠가 프로덕션이 깨진다. 메이저는 항상 사람이 릴리스 노트를 읽고 처리한다.
- 스케줄 부재: 업무 시간에 대량의 PR이 CI를 점유해 정작 기능 개발 파이프라인이 밀린다.
공통 해법은 “자동화의 강도를 위험도에 비례시키는 것”이다. 패치는 자동 병합, 마이너는 그룹 PR, 메이저는 개별 PR + 사람 처리. 이렇게 위험도별로 정책을 나눠야 자동화가 팀의 신뢰를 잃지 않고 지속 가능해진다.
마무리
의존성 자동화의 목표는 “사람의 판단을 없애는 것”이 아니라 “판단이 필요 없는 반복 작업에서 사람을 해방하는 것”이다. 작고 안전한 패치는 도구가 병합하게 두고, 사람은 파괴적 변경과 보안 판단에 집중한다. 시작은 Dependabot의 최소 설정으로 충분하고, 정교한 제어가 필요해지면 Renovate로 넘어가면 된다. 핵심 원칙은 하나다. 위험도에 비례한 자동화, 그리고 그것을 떠받치는 신뢰할 수 있는 테스트. 이 둘이 갖춰지면 의존성 업데이트는 미루다 폭발하는 부채가 아니라 조용히 흘러가는 배경 작업이 된다.
자주 묻는 질문
Q. Dependabot과 Renovate를 같은 저장소에 함께 써도 되나요?
A. 권장하지 않습니다. 두 도구가 같은 의존성에 각각 PR을 열어 중복과 충돌이 발생합니다. 하나를 선택하고, 마이그레이션 기간에만 잠시 병행하며 스캔 대상을 분리하세요.
Q. 자동 병합을 켜는 것이 정말 안전한가요?
A. 테스트 스위트의 신뢰도에 달려 있습니다. 패치·다이제스트 같은 저위험 업데이트에 한정하고, 필수 상태 검사가 걸린 브랜치 보호와 함께 쓴다면 대부분 안전합니다.
Q. 업데이트 PR이 너무 자주 열려 부담스럽습니다. 어떻게 줄이나요?
A. 세 가지를 조합하세요. schedule로 빈도를 낮추고, groupName으로 패키지를 묶고, 동시 PR 상한(prConcurrentLimit 또는 open-pull-requests-limit)을 설정합니다. 여기에 개발 의존성 패치 자동 병합까지 더하면 실제로 봐야 할 PR만 남습니다.