GitHub Actions에서 npm ci가 실패하면 lockfile과 Node 버전을 어떻게 확인하나요?
CI의 npm ci 실패는 package.json과 package-lock.json의 일치 여부부터 봅니다. 로컬 npm install 성공과는 다른 조건입니다. Node·npm 버전, 실행 폴더와 설치 옵션을 비교해 로그의 첫 원인을 수정합니다.
적용 조건·범위: 중요한 것은 예시의 24.x를 모든 프로젝트에 강제하는 것이 아니라 프로젝트 기준과 CI 기준을 일치시키는 것입니다.
본문 근거: docs.npmjs.com/cli/commands/npm-ci/ · docs.npmjs.com/files/package-lock.json · docs.github.com/en/actions/tutorials/build-and-test-code/nodejs
증상
GitHub Actions에서 다음 단계가 실패합니다.
- run: npm ci
로컬에서는 npm install 후 프로젝트가 실행될 수 있지만 CI에서는 설치 단계에서 멈춥니다.
환경
GitHub Actions
Node.js
npm
package.json
package-lock.json
목차
- npm install과 npm ci 차이
- 최소 재현
- 원인 1: lockfile 불일치
- 원인 2: Node/npm 버전 차이
- 원인 3: package manager 혼용
- install 옵션 차이도 확인합니다
- 수정 전
- 수정 후
- 실습 프로젝트 전체 코드
- 추가 점검
- 학습 완료 체크리스트
- TaskFlow 프로젝트 적용
- 실습 경로·결과·복구
- 다음 학습
- 공식 문서
- 단독 실습과 결과 확인
npm install과 npm ci 차이
npm install은 package.json을 기준으로 의존성을 설치하고 필요한 경우 package-lock.json을 갱신할 수 있습니다.
npm ci는 다른 목적을 가집니다.
npm install
→ 개발 중 의존성 추가·갱신 가능
npm ci
→ lockfile을 그대로 재현하는 clean install
npm ci의 핵심 특성은 다음과 같습니다.
- package-lock.json이 필요합니다.
- package.json과 lockfile이 맞지 않으면 실패합니다.
- 기존 node_modules를 제거한 뒤 설치합니다.
- package.json이나 package-lock.json을 수정하지 않습니다.
CI에서 이 엄격함은 단점이 아니라 재현성을 높이기 위한 동작입니다.
최소 재현
예를 들어 package.json에는 새 패키지가 추가되었지만 package-lock.json은 이전 상태라고 가정합니다.
{
"dependencies": {
"axios": "^1.0.0",
"zod": "^4.0.0"
}
}
그런데 lockfile이 axios만 알고 있다면 npm ci는 두 파일의 상태가 맞지 않는다고 판단할 수 있습니다.
흔한 발생 흐름은 다음과 같습니다.
package.json 수동 수정
↓
package-lock.json 갱신 안 함
↓
package.json만 commit
↓
CI에서 npm ci
↓
실패
원인 1: lockfile 불일치
가장 먼저 로컬에서 clean install을 재현합니다.
npm ci
로컬에서도 같은 오류가 발생한다면 CI 설정 이전에 저장소의 의존성 상태를 수정해야 합니다.
정상적인 수정 흐름은 다음과 같습니다.
npm install
npm ci
npm run build
git status
npm install로 lockfile을 현재 package.json과 맞춘 뒤 package-lock.json 변경을 확인합니다.
git diff -- package.json package-lock.json
의도한 변경이면 package.json과 package-lock.json을 함께 commit합니다.
lockfile을 이유 없이 삭제하고 CI가 알아서 새로 만들게 하는 방식은 npm ci의 목적과 맞지 않습니다.
원인 2: Node/npm 버전 차이
로컬과 CI의 Node 버전이 크게 다르면 의존성 설치나 build 결과가 달라질 수 있습니다.
GitHub Actions에서는 setup-node로 사용할 Node 버전을 명시할 수 있습니다.
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24.x"
cache: "npm"
프로젝트가 요구하는 Node 버전이 따로 있다면 package.json의 engines나 .nvmrc 같은 기준과 workflow를 맞춥니다.
{
"engines": {
"node": "24.x"
}
}
중요한 것은 예시의 24.x를 모든 프로젝트에 강제하는 것이 아니라 프로젝트 기준과 CI 기준을 일치시키는 것입니다.
원인 3: package manager 혼용
저장소에 다음 파일이 동시에 존재하면 어떤 package manager가 기준인지 불분명해질 수 있습니다.
package-lock.json
pnpm-lock.yaml
yarn.lock
의도적인 마이그레이션 중이 아니라면 프로젝트 기준 package manager와 lockfile을 하나로 정리합니다.
npm 프로젝트라면 다음처럼 운영할 수 있습니다.
package manager: npm
lockfile: package-lock.json
CI install: npm ci
pnpm이나 Yarn을 사용한다면 해당 package manager의 고정 설치 방식과 lockfile을 사용해야 합니다.
install 옵션 차이도 확인합니다
npm 공식 문서는 lockfile을 만들 때 dependency tree에 영향을 주는 특정 옵션을 사용했다면 npm ci에서도 같은 설정이 필요할 수 있다고 설명합니다.
예를 들어 로컬에서 항상 --legacy-peer-deps를 사용해 lockfile을 만들었다면 CI만 기본 설정으로 npm ci를 실행할 때 차이가 생길 수 있습니다.
이 경우 임시로 CI 명령 뒤에 옵션을 붙이는 것보다 왜 해당 설정이 필요한지 확인하고 프로젝트 .npmrc로 명시할지를 판단합니다.
수정 전
steps:
- uses: actions/checkout@v7
- run: npm ci
- run: npm run build
Node 버전이 명시되지 않았고 저장소의 lockfile이 현재 package.json과 다르다면 재현성이 떨어집니다.
수정 후
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
actions/checkout과 setup-node의 major version은 GitHub 공식 문서의 현재 예시와 프로젝트 정책을 확인해 사용합니다.
추가 점검
npm ci는 통과하지만 build가 실패합니다
설치 문제는 해결된 것입니다. 다음 단계의 TypeScript, lint, 환경변수, framework build 로그를 확인합니다.
cache를 지우면 해결될 것 같아서 먼저 삭제합니다
cache는 원인을 가릴 수 있지만 첫 번째 해결책으로 사용하지 않습니다. lockfile과 버전 기준을 먼저 확인합니다.
package-lock.json을 .gitignore에 넣습니다
애플리케이션 저장소에서 npm ci를 사용할 계획이라면 lockfile을 commit하는 것이 재현성에 중요합니다.
이 주제만 따로 연습하기
CI 계정 없이 잠금 파일 불일치를 판정합니다. 입력은 “package.json에는 axios와 zod가 있으나 현재 lockfile에는 zod가 없고, npm ci 로그에 package.json과 lockfile을 동기화하라는 오류가 있으며 build 단계는 시작하지 않음”입니다.
| 확인 | 기대 결과 |
|---|---|
| 실패 단계 | install |
| 먼저 비교할 파일 | 같은 commit의 package.json과 package-lock.json |
| 수정 방향 | zod 추가 의도를 확인한 후 같은 Node/npm·옵션으로 lockfile 갱신, 두 파일 diff 검토 |
| 다음 검증 | npm ci 재실행 후 build, 이후 같은 commit의 원격 CI 결과 확인 |
자가 확인: build가 시작하지 않았는데 DNS를 고치거나 lockfile을 삭제하는 것이 원인 수정일까요? 아닙니다. 이 판정 실습은 제시한 로그의 원인을 좁히는 과제이며 npm ci 실행 성공을 대신하지 않습니다. 아래 ZIP의 workflow는 적용 자료이며 독립 설치 앱은 아닙니다.
학습 완료 체크리스트
- npm install과 npm ci 차이를 설명할 수 있습니다.
- package.json과 package-lock.json 불일치를 확인할 수 있습니다.
- CI에서 Node 버전을 명시할 수 있습니다.
- package manager 혼용 여부를 확인할 수 있습니다.
- install 단계와 build 단계를 구분해서 진단할 수 있습니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 관리하는 예제 앱입니다. 이 선택 실습에서는 앱을 배포하고 서버·설정·운영 결과를 확인합니다.
이번 주제: GitHub Actions npm ci 실패 해결: lockfile과 Node 버전 확인.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
이 글부터 시작하는 전체 프로젝트
시작 ZIP — deploy-step-05-domain-https-full-20260928을 새 폴더에 풀고, 이번 단계 완료 ZIP은 다른 폴더에 풀어 비교합니다. ZIP 안 프로젝트 폴더의 package.json 위치가 실행 루트입니다.
출발점은 Next.js 누적 TaskFlow와 같은 38단계 소스입니다. 프로젝트에는 홈·로그인·개인 작업 화면, 작업 추가·완료·삭제, Route Handler와 캐시 예제가 들어 있습니다. 설치 버전을 고정한 package-lock.json도 포함합니다.
이번 변경 이유: 같은 package-lock.json과 Node 버전으로 CI clean install·build를 실행할 설정을 추가합니다.
.github/workflows/ci.yml
- 시작 ZIP을 풀고
npm ci로 의존성을 설치합니다. Node.js 24와 npm을 사용합니다. - 위 변경 파일을 아래 전체 내용 또는 완료 ZIP의 같은 경로와 대조해 적용합니다. 다른 앱 파일은 유지합니다.
npm run typecheck,npm run build를 실행합니다. 오류가 있으면 첫 오류의 파일·명령을 기록하고 해결한 뒤 진행합니다.npm run setup-auth로 일회용 로컬 학습 계정을 설정한 후npm run dev를 실행합니다. 생성된.env.local과 입력 값은 공유하지 않습니다.
예상 결과: 홈과 로그인 화면은 공개됩니다. 로그인 전 /tasks는 로그인 화면으로 이동하고 /api/tasks는 401을 반환합니다. 로그인 후 작업 목록에서 추가·완료·삭제를 확인합니다. /cache-lab의 별도 캐시 실습은 npm run cache-api가 필요합니다.
저장·운영 범위: 작업은 단일 Node 프로세스 메모리에 저장되고 인증은 학습용 계정 예제입니다. 서버 재시작·서버리스 인스턴스 사이의 데이터 유지는 보장하지 않습니다. 로컬 실행 확인과 실제 운영 서비스 구축은 구분하며, 원격 배포·DNS·SSH·CI·롤백 결과는 직접 실행한 항목만 문서에 기록합니다.
전체 파일·코드 보기
이번 단계의 변경 파일 전체
.github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
나머지 앱 소스와 잠금 파일은 전체 완료 ZIP에 있습니다. 기존 코드 조각은 설명용이며 위 전체 변경 파일과 ZIP이 이 누적 실습의 기준입니다. 다음 글에는 이번 완료 ZIP을 시작본으로 가져갑니다.
이 회차는 아래 전체 TaskFlow 시작 ZIP을 기준으로 진행합니다. 이전 글의 코드를 모으지 않아도 되며, 완료 ZIP에는 이번 변경과 기존 앱 기능이 함께 들어 있습니다.
실습 프로젝트 전체 코드
이 절의 파일 목록은 기존 앱에 적용하는 참고 문서·설정 코드이며 실행 앱 전체가 아닙니다. 이 조각만 모은 폴더에서 npm ci/build를 실행하지 않습니다. 실행 가능한 전체 시작·완료 ZIP과 설치 루트는 아래 TaskFlow 프로젝트 적용 절에 별도로 안내되어 있습니다. 전체 ZIP에서는 package.json이 있는 프로젝트 폴더를 기준으로 해당 설치·빌드 절차를 따르세요.
.env.example
# 기존 앱에서 실제 사용하는 변수만 설정하세요. 값은 개발/운영 플랫폼에서 별도 관리합니다.
NEXT_PUBLIC_API_URL=
API_SECRET=
.github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
.nvmrc
24
docs/deployment-plan.md
# TaskFlow Deployment Plan
## Build
- package manager: npm
- install: npm ci
- build: npm run build
## Primary deployment
- platform: Vercel
- environment: Production
## Alternative deployment
- Firebase Hosting: static output일 때 검토
- Firebase App Hosting: server runtime이 필요할 때 검토
## Verification
- Production URL 응답
- 주요 화면 확인
- 환경변수 확인
- 주요 route 확인
- 현재 commit 확인
- rollback 가능 여부 확인
## Runtime decision
- static-only: no
- server runtime required: yes
- reasons:
- Route Handler
- server-side environment variables
## Primary
Vercel Production
## Firebase alternative
App Hosting 우선 검토
## Static variant
정적 결과물로 분리할 경우 Firebase Hosting 검토
## Production deployment
- deployment URL: 운영 문서에서 관리
- commit SHA: 배포마다 기록
- deployment status: 확인
- build log: 확인
- smoke test: 확인
## Domain
- canonical domain: 운영 문서에서 관리
- www policy: canonical로 redirect
- HTTPS: 확인
- DNS status: 확인
- auth callback / allowed origin: 확인
docs/server-access-runbook.md
# Server Access Runbook
## Connection
- host: 저장하지 않음
- user: 환경별 문서에서 별도 관리
- authentication: SSH key 우선
- private key: 저장소에 포함 금지
## Before upload
1. 대상 서버 확인
2. 현재 경로 확인
3. 배포 대상 경로 확인
4. 기존 파일 상태 확인
## After upload
1. 파일 존재 확인
2. owner / permission 확인
3. 서비스가 실제 경로를 읽는지 확인
4. HTTP 결과 확인
## Never commit
- private key
- password
- access token
- production secret
README.md
# 배포·서버 06 — deploy-step-06-ci-install
이 ZIP은 배포 준비·운영 문서와 해당 회차 설정/코드 조각을 누적한 적용 자료입니다. 실행 앱 전체는 포함하지 않습니다. 기존 deploy-step-00-build-ready 프로젝트에 해당 파일을 검토하여 적용하세요. 이 ZIP 루트에서 npm ci/build를 실행하는 구조가 아닙니다.
.env.example은 값이 빈 설정 안내입니다. 독자의 앱에서 사용하는 변수만 .env.local 또는 플랫폼의 해당 환경에 설정하세요. URL·계정·비밀키를 ZIP에 넣지 않습니다.
CI workflow는 이미 package.json/package-lock.json과 build 스크립트가 있는 기존 npm 프로젝트 루트에 적용합니다. actions/checkout@v7, actions/setup-node@v7, Node 24.x를 사용합니다. GitHub 원격 실행은 검증하지 않았습니다.
이 자료에는 실제 배포·DNS 변경·SSH 접속·메일/알림·롤백 결과가 없습니다. 운영 작업은 독자가 본인 환경과 권한을 확인한 후 수행해야 합니다. 문서의 확인 항목은 수행할 체크리스트이며 이미 통과했다는 기록이 아닙니다.
이전 체크포인트
deploy-step-05-domain-https
변경 파일
.github/
└─ workflows/
└─ ci.yml
실행 확인
먼저 로컬에서 다음 명령을 실행합니다.
npm ci
npm run build
그다음 workflow를 push하고 GitHub Actions 결과를 확인합니다.
실제 GitHub Actions 실행 결과는 이 본문 작성 시점에 특정 저장소에서 실행한 것으로 간주하지 않습니다. 독자는 자신의 저장소에서 workflow 성공 여부를 확인해야 합니다.
완료 체크포인트
deploy-step-06-ci-install
실습 경로·결과·복구
독자 질문
왜 로컬 설치는 되는데 GitHub Actions의 npm ci는 실패할까요?
배포 전제
실패한 Actions 실행 로그와 현재 package.json·package-lock.json, 프로젝트에서 정한 Node 버전이 필요합니다. 운영 배포 설정을 건드리기 전에 별도 작업 브랜치에서 재현합니다.
실행 단계
- Actions의 첫 오류 행을 읽고 설치 단계 실패인지 build 단계 실패인지 표시합니다. workflow의 setup-node 버전과 로컬 node
--version을 비교합니다. - 같은 Node 버전에서 프로젝트 루트의 npm ci를 실행합니다. lockfile 불일치면 package.json 변경 의도를 확인해 잠금 파일을 정상 재생성하고 두 파일을 함께 커밋합니다. 캐시 삭제로 불일치를 감추지 않습니다.
- 브랜치에 push해 해당 commit의 Actions 재실행에서 npm ci 다음 build까지 통과했는지 확인합니다.
성공·실패 확인
로컬 npm ci 종료 코드 0은 로컬 설치만 증명합니다. 원격 Actions의 동일 commit에서 설치·build 단계가 모두 녹색일 때 CI 복구가 확인됩니다. 여전히 실패하면 로그의 첫 실패 패키지, lockfile 생성 환경과 Node 버전을 다시 대조합니다.
복구와 검증 상태
수정으로 의존성이 바뀌었다면 원래 lockfile과 package.json을 Git에서 함께 복원해 재현 가능한 기준으로 돌아갑니다. 원격 CI 재실행과 서비스 배포는 이 글의 작성 과정에서 미확인입니다.
다음 학습
다음 글에서는 CI 설치는 통과하지만 Vercel build 단계에서 실패하는 경우를 build log, 환경변수, 타입 오류, Node 설정으로 나눠 확인합니다.
다음 글: Vercel Next.js 배포 실패 해결 체크리스트
다음 회차 확인 과제: 07회차에서는 설치는 통과하지만 Vercel build가 실패하는 경우 첫 오류 행과 로컬 재현 결과를 대조합니다.
공식 문서
공식 문서 확인 기준: 2026-09-21
체크포인트 다운로드
글 소개·학습 준비·전체 목차

배포·서버 · 6 / 11 · 오류 해결 · GitHub Actions npm ci
로컬에서는 npm install이 정상인데 GitHub Actions에서 npm ci가 실패한다면 가장 먼저 package.json과 package-lock.json이 같은 의존성 상태를 표현하는지 확인해야 합니다.
npm 공식 문서에서 npm ci는 자동화 환경을 위한 clean install 명령으로 설명됩니다. package-lock.json이 필요하고 package.json과 lockfile이 맞지 않으면 lockfile을 자동 수정하지 않고 오류로 종료합니다.
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 11개 · 필수 7개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.