pnpm install 오류: lockfile과 workspace를 삭제 없이 먼저 분리 진단합니다
pnpm install 실패는 같은 메시지처럼 보여도 lockfile과 manifest의 불일치, workspace 패키지 누락·버전 불일치, CI의 pnpm 버전 차이, Docker 복사 순서, registry 인증으로 원인이 갈립니다. 가장 먼저 할 일은 node_modules나 pnpm-lock.yaml 삭제가 아니라 오류 코드, 실행 위치, pnpm 버전, 변경된 manifest를 보존하는 것입니다. 이 글은 진단 스냅샷을 만든 뒤 원인별 최소 변경을 적용하고 clean 환경에서 --frozen-lockfile로 재검증하는 절차를 제공합니다.
검증 기준: 2026-07-19 기준 pnpm 11.15.0 공식 릴리스와 pnpm 11.x 공식 문서를 확인했습니다. 예시는 pnpm 11.15.0으로 버전을 맞추지만, 실제 프로젝트가 다른 major를 고정했다면 임의 업그레이드하지 말고 그 고정 버전의 문서를 사용합니다.
- 오류 코드를 먼저 분류
- 수정 전 진단 스냅샷
- lockfile 수리 절차
- workspace 수리 절차
- CI와 Docker 예외
- 인증·경로·무결성 예외
- 최종 검증
- 공식 출처와 학습 경로
- 결론
1. 오류 코드를 먼저 분류합니다
lockfile 불일치와 workspace 불일치는 다른 오류입니다
pnpm 공식 Error Codes에서 ERR_PNPM_OUTDATED_LOCKFILE은 현재 manifest로 설치하려면 lockfile 변경이 필요한 상태를 뜻합니다. 반면 ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE는 workspace:로 요구한 패키지 또는 버전이 현재 workspace 안에 없다는 뜻입니다. 전자는 같은 pnpm 버전으로 lockfile을 갱신하는 경로이고, 후자는 workspace 포함 범위·패키지 이름·버전 범위를 고치는 경로입니다.
| 대표 신호 | 우선 원인 | 첫 확인 |
|---|---|---|
ERR_PNPM_OUTDATED_LOCKFILE |
manifest와 lockfile 불일치 | 변경된 모든 package.json과 pnpm 버전 |
ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE |
로컬 패키지 누락·이름·범위 불일치 | pnpm-workspace.yaml과 대상 패키지 name |
| CI에서만 frozen install 실패 | lockfile 미포함, 다른 작업 디렉터리, 버전 차이 | checkout 파일·cwd·pnpm --version |
| Docker에서 workspace 패키지 없음 | manifest·workspace 파일 복사 순서 또는 build context | COPY 대상과 .dockerignore |
| 401·403·패키지를 찾을 수 없음 | registry URL·인증·접근 권한 | 비밀값을 출력하지 않고 registry scope만 확인 |
ERR_PNPM_TARBALL_INTEGRITY |
다운로드 바이트와 lockfile checksum 불일치 | 오류의 다운로드 URL·proxy·registry 상태 |
에러 메시지의 첫 코드와 마지막 원인을 함께 보존합니다
긴 로그에서 마지막 줄만 복사하면 실제 오류 코드와 실패한 importer 경로가 빠질 수 있습니다. 첫 ERR_PNPM_* 코드, 실패한 workspace 패키지, 실행 명령, pnpm·Node 버전을 한 묶음으로 보존합니다. 여러 오류가 연쇄적으로 보이면 가장 먼저 발생한 해석·해결 오류부터 처리합니다. peer dependency 문제나 lifecycle script 실패는 lockfile 삭제로 해결할 문제가 아닙니다.

2. 수정 전 진단 스냅샷을 만듭니다
실행 위치·버전·변경 파일을 기록합니다
workspace 루트가 아닌 하위 디렉터리에서 실행했거나 로컬과 CI가 다른 pnpm major를 사용하면 같은 저장소에서도 다른 lockfile 해석 결과가 나올 수 있습니다. 먼저 읽기 전용 명령으로 현재 상태를 기록합니다. 토큰이 들어갈 수 있는 전체 config나 .npmrc 내용은 로그에 출력하지 않습니다.
pwd
node --version
pnpm --version
pnpm -r list --depth -1
git status --short -- package.json pnpm-lock.yaml pnpm-workspace.yaml
git diff -- package.json pnpm-lock.yaml pnpm-workspace.yaml
Windows PowerShell에서는 lockfile 자체를 수정하지 않고 현재 바이트의 해시를 기록할 수 있습니다. 이후 명령 전후 해시가 같다면 해당 단계가 lockfile을 쓰지 않았다는 근거가 됩니다.
pnpm --version
node --version
Get-Location
Get-FileHash -Algorithm SHA256 -LiteralPath .\pnpm-lock.yaml
pnpm 11의 dry-run으로 예상 변경을 먼저 봅니다
pnpm 11.8.0부터 pnpm install --dry-run은 실제 의존성 해석을 수행하고 무엇이 바뀔지 보고하지만 lockfile, manifest, node_modules에는 쓰지 않습니다. 다만 변경이 필요하다고 보고해도 완료된 dry run은 종료 코드 0일 수 있으므로, CI 판정은 출력만 보지 말고 최종 --frozen-lockfile 결과로 해야 합니다. pnpr server가 구성된 경로에서는 dry-run을 사용할 수 없다는 공식 예외도 있습니다.
pnpm install --dry-run
# 실제 파일 변경 여부는 별도로 확인합니다.
git diff --exit-code -- package.json pnpm-lock.yaml pnpm-workspace.yaml
3. lockfile 불일치 수리 절차
프로젝트가 고정한 pnpm 버전을 먼저 맞춥니다
lockfile을 만든 도구 버전이 다르면 불필요한 대규모 diff가 생길 수 있습니다. 루트 package.json의 packageManager를 팀이 승인한 정확한 버전으로 고정하고 로컬·CI·Docker가 같은 값을 쓰게 합니다. 여기서는 2026-07-19 최신 안정 릴리스인 pnpm 11.15.0을 예로 들지만, 기존 프로젝트가 10.x를 고정했다면 마이그레이션 결정 없이 11.x로 올리지 않습니다.
{
"name": "acme-workspace",
"private": true,
"packageManager": "pnpm@11.15.0"
}
manifest 의도가 맞다면 lockfile만 최소 갱신합니다
의존성 추가·제거·버전 변경이 의도된 것인지 먼저 코드 리뷰 범위와 대조합니다. 의도가 맞고 pnpm 버전도 일치하면 --lockfile-only로 해석 결과를 갱신합니다. 공식 pnpm install 문서에 따르면 이 옵션은 pnpm-lock.yaml과 필요 시 package.json만 갱신하고 node_modules에는 쓰지 않으므로, 두 파일의 diff를 모두 확인해야 합니다.
pnpm --version
pnpm install --lockfile-only
git diff -- package.json pnpm-lock.yaml
pnpm install --frozen-lockfile
--frozen-lockfile은 lockfile을 만들거나 갱신하지 않으며 manifest와 맞지 않거나 lockfile이 없으면 실패합니다. lockfile이 있는 CI에서는 기본값이 true입니다. 로컬 수리 후 이 명령이 통과해야 CI로 보낼 수 있는 재현 가능한 상태라고 판단합니다.
–fix-lockfile과 checksum 갱신은 용도가 제한됩니다
--fix-lockfile은 깨진 lockfile 항목을 자동으로 채우는 옵션이지 manifest 불일치를 무시하는 옵션이 아닙니다. pnpm 11.4.0 이후 tarball checksum 불일치는 기본적으로 강한 실패이며 --force나 --fix-lockfile이 우회하지 않습니다. 사설 registry가 합법적으로 같은 버전의 tarball을 다시 만들었다는 사실과 새 바이트를 별도로 검증한 경우에만 좁은 범위의 --update-checksums를 검토합니다.
lockfile 삭제는 재해석 범위를 승인한 경우에만 고려합니다
단순한 ERR_PNPM_OUTDATED_LOCKFILE은 삭제 사유가 아닙니다. lockfile을 처음부터 다시 만들면 허용 범위 안의 전이 의존성, peer 해석, integrity 값이 넓게 바뀔 수 있습니다. major 마이그레이션이나 복구 불가능한 병합 손상처럼 재해석 자체가 승인된 경우에만 기존 파일을 별도로 보존하고, 고정된 pnpm 버전으로 재생성한 뒤 전체 diff·테스트·clean install을 검토합니다.
4. workspace 패키지 수리 절차
pnpm-workspace.yaml의 포함·제외 glob을 확인합니다
pnpm-workspace.yaml은 workspace 루트를 정의합니다. packages가 없으면 루트 패키지만 포함되고, 사용자 지정 glob이 있어도 루트는 항상 포함됩니다. 새 패키지가 glob 밖에 있거나 제외 패턴에 걸리면 디렉터리가 존재해도 workspace 패키지로 해석되지 않습니다. 공식 pnpm-workspace.yaml 문서의 포함 규칙과 실제 디렉터리 구조를 대조합니다.
packages:
- 'apps/*'
- 'packages/*'
- '!**/fixtures/**'
의존성 키·대상 name·workspace 범위를 함께 맞춥니다
workspace: 프로토콜은 로컬 workspace 패키지만 사용하도록 강제합니다. 대상이 없거나 요청 범위와 버전이 맞지 않으면 registry로 조용히 대체하지 않고 설치가 실패합니다. 소비자 manifest의 의존성 키, 대상 패키지의 name과 version, 선언한 range를 한 세트로 확인합니다.
{
"name": "@acme/ui",
"version": "1.4.0"
}
{
"name": "@acme/web",
"dependencies": {
"@acme/ui": "workspace:^1.4.0"
}
}
별칭과 상대 경로는 명시 문법을 사용합니다
의존성 키를 대상 패키지 이름과 다르게 쓰려면 "ui": "workspace:@acme/ui@*"처럼 공식 alias 문법을 사용합니다. 상대 경로가 의도라면 workspace:../ui 문법을 쓸 수 있지만 이동에 민감하므로 패키지 이름 기반이 더 읽기 쉬운지 검토합니다. 자세한 보장은 pnpm Workspace 문서를 따릅니다.
workspace 그래프를 확인한 뒤 lockfile을 갱신합니다
pnpm -r list --depth -1 결과에 기대한 패키지가 나타나는지 먼저 확인합니다. 누락됐다면 lockfile보다 glob과 패키지 manifest를 고칩니다. 그래프가 맞아진 뒤 동일한 고정 버전으로 pnpm install --lockfile-only를 실행하고, importer 변경이 의도한 패키지에만 생겼는지 검토합니다.
5. CI와 Docker에서만 실패할 때
CI는 checkout·pnpm·Node·작업 디렉터리를 고정합니다
로컬 성공과 CI 실패의 가장 흔한 차이는 도구 버전과 파일 집합입니다. CI가 하위 디렉터리에서 실행되거나 sparse checkout으로 workspace manifest를 누락했는지 확인합니다. 캐시 키가 오래됐더라도 정확한 lockfile과 고정 버전의 frozen install이 성공해야 하며, 캐시 삭제를 정답으로 삼지 않습니다. CI별 예시는 pnpm Continuous Integration 공식 가이드와 대조합니다.
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5
with:
version: 11.15.0
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm -r list --depth -1
위 version은 루트 packageManager와 같아야 합니다. 조직이 pnpm major와 Node 버전을 별도 파일로 관리한다면 그 단일 기준을 CI와 로컬이 공유하도록 구성합니다.
Docker는 dependency 레이어에 필요한 파일을 먼저 복사합니다
Docker build context에 pnpm-lock.yaml, pnpm-workspace.yaml, 패치 파일과 필요한 설정이 없으면 로컬과 다른 설치가 됩니다. pnpm 공식 Docker 가이드는 일반 설치 레이어와 lockfile 기반 pnpm fetch 패턴을 제시합니다. 다음 예시는 pnpm 버전을 고정하고 lockfile로 먼저 fetch한 뒤 소스를 복사해 offline frozen install을 수행합니다.
FROM ghcr.io/pnpm/pnpm:11.15.0 AS base
RUN pnpm runtime set node 24 -g
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm fetch --prod
COPY . .
RUN pnpm install -r --offline --prod --frozen-lockfile
RUN pnpm -r --if-present run build
pnpm fetch 공식 문서에 따르면 fetch는 lockfile을 사용하고 package manifest를 무시해 Docker 캐시를 안정화합니다. 다만 로컬 file: 의존성은 fetch 시점에 경로가 없을 수 있어 건너뜁니다. 그런 의존성은 소스 복사 후 install 단계에서 실제 경로가 build context에 포함됐는지 별도로 검증해야 합니다. 전체 패턴은 Working with Docker를 기준으로 조정합니다.
6. 인증·경로·무결성 예외
401·403은 lockfile보다 registry 인증을 봅니다
사설 패키지 다운로드가 401 또는 403이면 scope의 registry URL, CI secret 주입, 토큰 권한과 만료를 확인합니다. lockfile을 다시 만들어도 접근 권한은 생기지 않습니다. 진단 로그에는 토큰이나 전체 .npmrc를 출력하지 말고, 오류에 표시된 host와 package scope만 기록합니다.
file:·link: 의존성은 경로와 build context를 확인합니다
file: 또는 link: 대상이 로컬에는 있지만 CI checkout이나 Docker context에는 없으면 설치가 실패합니다. 대소문자 구분이 다른 운영체제도 고려합니다. 공식 install 문서는 frozen이 아닌 설치가 file: 대상의 최신 정보를 검사하며, 대상이 제거된 환경에서는 production install도 실패할 수 있음을 설명합니다.
병합 충돌은 manifest 의도를 먼저 확정합니다
여러 브랜치가 같은 importer를 바꿨다면 lockfile 텍스트만 수동 조합하기보다 양쪽 package.json의 최종 의도를 먼저 확정합니다. 그 다음 고정된 pnpm 버전으로 lockfile을 갱신하고 예상하지 않은 전이 의존성 변화가 없는지 diff를 검토합니다. 충돌 표시를 지웠다는 사실만으로 유효한 lockfile이 된 것은 아닙니다.
store prune은 기본 초기화 명령이 아닙니다
pnpm store prune은 사용되지 않는 패키지와 로컬 metadata cache를 정리합니다. 공식 오류 문서는 registry가 같은 버전의 파일을 바꿔 오래된 metadata가 남은 특정 integrity 사례에서 재시도 방법으로 언급하지만, 먼저 오류가 출력한 다운로드 URL이 올바른지 확인하라고 경고합니다. 일반적인 outdated lockfile이나 workspace 불일치에 이 명령을 먼저 쓰지 않습니다.
7. 최종 검증: 로컬 성공이 아니라 재현 가능성을 확인합니다
로컬에서 frozen install과 workspace 그래프를 검사합니다
수리 후에는 처음 기록한 버전과 경로에서 다음 순서를 실행합니다. 마지막 build·test 명령은 프로젝트에 실제 존재하는 script와 필터로 바꿉니다. 존재하지 않는 script를 억지로 추가하는 것은 설치 오류 수리 범위를 벗어납니다.
pnpm --version
pnpm install --frozen-lockfile
pnpm -r list --depth -1
# 저장소에 존재하는 검증 script로 교체합니다.
pnpm -r --if-present run build
pnpm -r --if-present run test
clean CI·Docker에서도 같은 입력으로 재검증합니다
| 환경 | 통과 조건 | 실패 시 되돌아갈 단계 |
|---|---|---|
| 로컬 | 고정 pnpm 버전, frozen install, 예상 workspace 목록 | 버전·manifest·glob 감사 |
| 새 checkout 또는 clean CI | 로컬 cache 없이 frozen install 성공 | checkout 파일·cwd·secret·Node 버전 |
| Docker | build context만으로 fetch/install/build 성공 | COPY 순서·file 의존성·ignore 규칙 |
| diff 검토 | 의도한 manifest와 importer만 변경 | pnpm 버전 차이·불필요한 재해석 |
| 애플리케이션 검증 | 실제 build·test·타입체크 통과 | peer·native module·lifecycle script |
검증 실패를 cache 문제로 덮지 않습니다
cache를 비우고 우연히 성공한 결과만으로는 lockfile 일관성을 입증할 수 없습니다. 반대로 cache가 없어도 고정된 pnpm 버전, 동일한 manifest와 lockfile, 올바른 registry 권한으로 frozen install이 성공해야 합니다. 실패 환경의 입력 차이를 표로 남기면 다음 오류에서도 같은 절차를 재사용할 수 있습니다.
공식 출처와 내부 학습 경로
pnpm 공식 문서·릴리스
- pnpm 11.15.0 공식 릴리스
- pnpm install 옵션
- pnpm Error Codes
- Workspace와 workspace: 프로토콜
- pnpm-workspace.yaml
- Continuous Integration
- Working with Docker
- pnpm fetch
내부 학습 경로
- 의존성 해석: npm ERESOLVE 의존성 충돌 해결
- CI 환경: GitHub Actions lockfile·Node 버전 오류
- 배포 빌드: Vercel Next.js 빌드 오류 체크리스트
결론: 삭제보다 오류 분류와 frozen 재현이 먼저입니다
pnpm install 오류의 안전한 해결 순서는 오류 코드 보존, 실행 위치·pnpm·Node 버전 기록, manifest와 workspace 그래프 확인, 원인별 최소 변경, clean 환경의 frozen install입니다. ERR_PNPM_OUTDATED_LOCKFILE이면 승인된 manifest 의도를 같은 pnpm 버전으로 lockfile에 반영하고, workspace 오류이면 glob·패키지 이름·버전 범위를 먼저 고칩니다. CI와 Docker에서만 실패하면 checkout·cwd·버전·복사 순서·인증을 비교합니다.
최종 통과 조건은 로컬 node_modules가 우연히 설치됐다는 사실이 아닙니다. 고정된 pnpm 버전과 같은 manifest·lockfile만으로 새 checkout과 Docker에서도 --frozen-lockfile이 성공하고, 기대한 workspace 목록과 실제 build·test가 통과해야 합니다. 이 조건을 만족하면 lockfile을 삭제하지 않아도 오류 원인과 해결 결과를 설명 가능한 상태로 남길 수 있습니다.