한 줄 요약: Verdaccio 는 npmjs uplink 를 lazy 캐시로 묶어주는 사설 npm registry 임.

v6 는 Node.js 18+ 만 요구하고 설정은 config.yaml 한 장에 모임. 캐시 용도로만 쓰는 환경이면 추가 인증 없이도 기본값으로 충분함.


개요

인터넷망에 직접 접근하지 못하는 장비들에게 외부 npm 패키지를 제공해야 하는 상황에서 Verdaccio 의 proxy 기능을 활용하게 됨. 내부망 장비가 npmjs.com 으로 직접 나가지 못하면 npm install 자체가 실패하므로, public 패키지를 한 번 끌어와 사내에서 캐시로 재사용하는 중계점이 필요함.

Verdaccio 는 그 중계점을 한 프로세스로 합친 사설 npm registry 임. 모든 public 패키지를 미리 미러링하지 않고 요청 시점에만 npmjs 에서 끌어와 로컬에 캐시하는 lazy 방식이라 디스크가 가벼움.

이 글은 v6 기준으로 아래 두 단계를 한 흐름으로 정리함.

  • Docker 로 기동 (부팅 시 자동 재기동은 --restart 정책에 위임)
  • 클라이언트(.npmrc)에서 글로벌 설치까지 깨지지 않도록 registry 등록
항목
현재 메이저Verdaccio 6 (실측 6.5.2)
Node.js 요구사항v18 이상 (공식 권장 — Node 22 LTS)
기본 listenlocalhost:4873 — Docker 이미지는 0.0.0.0:4873 으로 띄움
설정 파일config.yaml 한 장 — Docker 이미지 기본 경로 /verdaccio/conf/config.yaml
Uplinknpmjs (URL https://registry.npmjs.org/) — 미캐시 요청만 lazy 프록시
로그 키 변경v5.22.0+ 부터 logs:log: 로 키 이름 변경. v6 도 logs: 호환은 되지만 권장은 log:

검증 환경: Verdaccio 6.5.2 (공식 이미지 verdaccio/verdaccio:latest, 내부 Node 22.22.1), 클라이언트는 호스트의 npm 11.6.2 / Node 24.12.0. 호스트 포트 14873 → 컨테이너 4873 매핑.


결론

어떤 형태로 굳힐 것인가 — 3가지 운영 결정

운영 환경에 적용하기 전에 먼저 정해두면 후속 설정이 단순해지는 결정 3가지.

결정 항목권장값이유
기동 방식Docker (verdaccio/verdaccio:6) + bind volume호스트 Node 버전과 분리되고, 업그레이드는 이미지 태그 교체로 끝남. --restart unless-stopped 한 줄로 lifecycle 위임
Web UI외부망 접근이 가능한 환경이면 web.enabled: falseUI 가 켜져 있으면 패키지 메타·다운로드 통계가 비인증 사용자에게 노출됨
Uplink 범위사내 빌드에 필요한 단일 npmjs uplink 만 유지여러 uplink 를 묶으면 캐시 충돌·메타 병합 비용이 커지고, 디버깅 시 어느 origin 인지 추적이 어려워짐

클라이언트 registry 등록 — npm set registry 의 함정

npm set registry http://... 만 쳐두면 사용자 수준 ~/.npmrc 에만 저장되어 sudo npm install -g <pkg> 시 root 의 npmrc 를 보느라 사설 registry 를 못 찾는 사고가 흔함.

글로벌 설치까지 사설 registry 를 타게 만들려면 시스템 단에 등록해야 함.

범위파일명령 또는 형식
명령어 1회(없음)npm install --registry http://...:4873/
사용자 전역~/.npmrcnpm set registry http://...:4873/
프로젝트 전용<repo>/.npmrcnpm set registry http://...:4873/ --location project
시스템 전역/etc/npmrc (또는 globalconfig)직접 작성 — registry=http://...:4873/

npm config ls -l | grep ^globalconfig 로 그 시스템의 글로벌 npmrc 위치를 먼저 확인함 — 배포판마다 /etc/npmrc, /usr/local/etc/npmrc, /usr/etc/npmrc 로 갈림.


적용 절차

1단계 — Docker 로 기동

공식 이미지의 표준 마운트 경로 3개 — /verdaccio/conf (설정), /verdaccio/storage (캐시 데이터 보관), /verdaccio/plugins (확장).

컨테이너 내부 사용자는 UID 10001, GID 65533 이라 bind mount 디렉토리 권한을 미리 맞춤.

V_PATH=/srv/verdaccio
mkdir -p $V_PATH/{conf,storage,plugins}
sudo chown -R 10001:65533 $V_PATH
 
docker run -d --name verdaccio \
  -p 4873:4873 \
  -v $V_PATH/conf:/verdaccio/conf \
  -v $V_PATH/storage:/verdaccio/storage \
  -v $V_PATH/plugins:/verdaccio/plugins \
  --restart unless-stopped \
  verdaccio/verdaccio:6

설정을 따로 마운트하지 않은 상태로 한 번 띄우면 컨테이너 내부에 기본 config.yaml 이 생김 — 이 파일을 호스트로 복사해 편집 후 다시 마운트하는 흐름이 안전함.

docker cp verdaccio:/verdaccio/conf/config.yaml $V_PATH/conf/config.yaml

--restart unless-stopped 플래그가 부팅 시 자동 기동·crash 시 재시작을 모두 담당함. systemd unit 은 다른 서비스가 After=verdaccio.service 로 의존성 선언이 필요할 때만 추가하면 됨 — 단순 캐시 용도면 docker daemon 이 lifecycle 을 그대로 가져가도 충분함.

2단계 — config.yaml 운영 형태

기본 config 에서 3가지만 손보면 캐시 운영 형태가 됨 — listen 0.0.0.0, web 비활성, 로그 파일 출력.

# /verdaccio/conf/config.yaml — v6 권장 키 이름 기준
 
storage: /verdaccio/storage/data
plugins: /verdaccio/plugins
 
listen:
  - 0.0.0.0:4873
 
web:
  enabled: false
 
uplinks:
  npmjs:
    url: https://registry.npmjs.org/
 
packages:
  '@*/*':
    access: $all
    proxy: npmjs
  '**':
    access: $all
    proxy: npmjs
 
server:
  keepAliveTimeout: 60
 
middlewares:
  audit:
    enabled: true
 
log:
  type: file
  path: /verdaccio/storage/verdaccio.log
  format: pretty-timestamped
  level: http

web.enabled: false 는 v6 기준임 (v4 시절의 enable: 은 deprecated). log: 도 v5.22.0+ 에서 logs: 에서 변경된 키이므로 새 설정은 단수형으로 적음.

3단계 — 클라이언트 registry 등록

사용자별·프로젝트별·시스템별 세 단계 중 sudo npm install -g 까지 깨지지 않게 하려면 시스템 단(/etc/npmrc)에 등록하는 게 정공법.

# 시스템 글로벌 npmrc 위치 확인
npm config ls -l | grep "^globalconfig ="
 
# 위치 확인 후 그 경로에 작성
sudo tee /etc/npmrc >/dev/null <<'EOF'
registry=http://<host>:4873/
EOF

이 한 줄만 등록해두면 npm install·sudo npm install -g 모두 사설 registry 를 거쳐 npmjs 로 lazy 프록시됨.


상세

Verdaccio 가 미러링 도구가 아니라 lazy proxy 인 게 디스크 비용 측면에서 핵심임. 클라이언트가 lodash 를 요청하면 다음 순서로 동작함.

  1. storage/lodash/package.json 이 있는지 검사
  2. 없으면 uplinks.npmjs 의 URL 로 메타데이터 가져오기
  3. 요청된 버전의 tarball 만 받아 storage 에 저장
  4. 다음 동일 요청부터는 storage 에서 응답

따라서 공개된 npm 전체를 미리 받지 않고도 사내 빌드에 실제 쓰이는 패키지만 누적되는 형태가 됨. 이 동작은 packages.<glob>.proxy: npmjs 가 붙어 있을 때만 활성화됨.

Verdaccio 는 풀 미러(full mirror) 기능을 제공하지 않음 — npmjs 전체 패키지를 사전에 받아두는 옵션은 설계상 없음. 도입 시점의 요건이 “사내에서 실제 쓰이는 패키지만 사용 시점에 캐시” 였으므로 lazy 방식이 그대로 맞아떨어졌음.

log: 키 — v5.22.0+ 에서 이름이 바뀜

이 글의 config.yaml 예시가 단수형 log: 를 쓴 이유는 단순함. v5.22.0 부터 logger 속성명이 logslog 로 바뀌었음.

v6 는 logs: 호환을 유지하지만 docs 가 권장하지 않는다고 명시함. 새로 작성하는 config 면 단수형 log: 만 쓰면 됨.

Since v5.22.0 the logger property is renamed from `logs` to `log`,
but still compatible with v6 but not recommended to use.

레벨은 fatal·error·warn·info·http·debug·trace 7단계. http 는 요청 한 줄씩 찍히므로 운영 초기 디버그용으로 적당하고, 안정화된 후에는 info 로 내리는 패턴.


테스트

테스트 환경: verdaccio/verdaccio:latest (Verdaccio 6.5.2, 내부 Node 22.22.1) 을 호스트 포트 14873 에 매핑해 기동. 클라이언트는 호스트의 npm 11.6.2.

1. 기동 직후 헬스체크

docker run -d --name blog-test-verdaccio -p 14873:4873 verdaccio/verdaccio:latest
until curl -fsS http://127.0.0.1:14873/-/ping >/dev/null; do sleep 1; done
curl -fsS http://127.0.0.1:14873/-/ping
{}

/-/ping 은 빈 객체를 반환하면 정상임 — npm CLI 도 같은 endpoint 로 헬스체크를 함.

.npmrc 의 registry 를 verdaccio 로 두고 npmjs 의 패키지를 install 하면 verdaccio 가 npmjs 에서 한 번 끌어와 storage 에 캐시함.

mkdir -p /tmp/blog-test-verdaccio/consumer1 && cd $_
echo "registry=http://127.0.0.1:14873/" > .npmrc
 
npm install lodash@4.17.21 --no-fund --no-audit
docker exec blog-test-verdaccio ls /verdaccio/storage/data/
added 1 package in 506ms
---
lodash

3. 캐시 적중 — 두 번째 install 은 storage 에서

같은 패키지를 다른 디렉토리에서 다시 install 하면 verdaccio 가 이미 storage 에 있는 tarball 을 그대로 응답함. npm CLI 의 로컬 캐시(--cache)가 영향을 주지 않도록 consumer2 에 별도 캐시 디렉토리를 둠.

mkdir -p /tmp/blog-test-verdaccio/consumer2 && cd $_
echo "registry=http://127.0.0.1:14873/" > .npmrc
npm install lodash@4.17.21 --no-fund --no-audit --cache=$PWD/.npm-cache
added 1 package in 415ms

첫 install (506ms) 보다 두 번째 install (415ms) 이 짧음 — verdaccio 가 외부 npmjs 라운드트립을 건너뛰고 storage 의 tarball 을 즉시 응답하기 때문임. 단일 패키지 차이는 작아 보여도 사내 빌드처럼 수십~수백 개 의존성이 같이 install 될수록 격차가 누적됨.


참고