SNS 공유 미리보기
npm 배포 자동화 마무리하기: OIDC로 latest 승격과 dist-tag 관리하기
9월 30일 추가된 npm dist-tag OIDC 권한을 바탕으로 후보 게시, 소비자 검증, 정식 승격과 복구를 나누는 배포 절차를 정리합니다.
카카오톡, 페이스북 등 SNS 공유 시
위와 같은 형태로 노출됩니다.
npm 배포 자동화 마무리하기: OIDC로 latest 승격과 dist-tag 관리하기
💡 npm 패키지를 CI에서 자동으로 올려두고, 마지막에 latest를 옮기는 일만 수동으로 남겨둔 프로젝트가 있을 거예요. 게시용 인증을 OIDC로 바꿨는데도 태그를 관리하려고 장기 토큰 하나를 계속 보관하는 경우입니다. 이번 주에는 바로 이 마지막 구간을 점검해 볼 만한 변경이 나왔어요.
GitHub는 2026년 9월 30일, npm trusted publishing에서 dist-tag 작업을 별도 권한으로 허용할 수 있다고 발표했습니다. 새 설정과 기존 설정 모두 기본값은 꺼짐이에요. 필요한 워크플로만 명시적으로 허용해야 합니다. 이번 글은 이 변경을 바탕으로, 패키지 게시와 정식 버전 승격을 나누는 작은 배포 절차를 만들어 봅니다. 공식 변경 내용
STEP 1버전은 결과물이고, 태그는 그 결과물을 가리켜요
설명을 위해 @example/widget이라는 가상의 패키지를 생각해 볼게요. 2.1.0은 특정 배포 결과물의 버전이고, next나 latest는 어느 버전을 설치할지 알려주는 이름입니다. 같은 버전이 두 태그에서 동시에 가리켜져도 괜찮아요.
보통 후보 버전은 next로 안내하고 검토가 끝나면 같은 버전을 latest로 승격하는 식으로 운영할 수 있어요. 다만 npm이 next를 특별한 안전 구역으로 취급하지는 않습니다. 이 이름의 의미와 누가 설치해야 하는지는 프로젝트가 정한 규칙입니다. 공개 패키지의 후보 버전도 외부에서 접근할 수 있으니 비공개 검토와 혼동하면 안 돼요.
예시 정책: next → 검증용, latest → 기본 설치용. 태그 이름 자체가 접근 권한이나 품질을 보장하지 않습니다.
npm publish는 별도 태그를 주지 않으면 기본적으로 latest를 갱신합니다. 후보를 따로 검증하려면 게시 단계에서 태그를 명시하는 것이 중요해요. 태그 없이 설치하는 사용자에게 무엇이 전달될지 함께 생각해야 합니다. npm dist-tag 동작
여기서 한 가지 더 구분할 게 있어요. latest를 예전 버전으로 되돌려도 이미 설치된 앱이나 잠금 파일이 자동으로 바뀌지는 않습니다. 배포 채널의 기본 대상을 돌리는 것과, 소비자 서비스 자체를 복구하는 것은 별도 작업이에요. 이 차이를 릴리스 문서에 적어두면 장애 중에 잘못된 기대를 줄일 수 있어요.
STEP 2YAML을 만들기 전에 실행 환경부터 맞춰요
OIDC는 실행 중인 워크플로의 신원을 확인해 필요한 때 단기 자격 증명을 받는 방식입니다. 장기 토큰의 보관·교체 부담을 줄이는 데 도움이 돼요. 그렇다고 잘못 작성된 워크플로까지 안전해지는 것은 아닙니다. 허용된 작업이 악성 코드나 잘못된 입력을 실행하면 권한을 가진 상태에서 문제가 생길 수 있어요. GitHub OIDC 개념
2026년 10월 5일 확인한 npm 문서는 GitHub-hosted runner, GitLab.com shared runner, CircleCI cloud를 지원 범위로 안내합니다. self-hosted runner는 이 흐름에 그대로 적용할 수 없어요. 서버가 있다는 이유만으로 기존 러너를 재사용하기 전에 지원 여부를 확인해야 합니다.
또한 일반 trusted publishing의 최소 버전만 보고 넘어가면 안 됩니다. dist-tag의 OIDC 인증은 npm 11 계열에서는 11.21.0 이상, npm 12 계열에서는 12.2.0 이상을 요구해요. 빌드 로그에 Node와 npm 버전을 남기고, 프로젝트에서 검증한 버전을 명시해 두세요. 숫자는 향후 바뀔 수 있으니 적용 시 현재 지원 요건을 다시 확인합니다.
위 역할 분리는 이 글의 운영 설계 예시입니다. npm의 직접 게시 허용 여부와 dist-tag 허용 여부는 독립적인 설정이에요.
npm 패키지 설정에서는 조직 또는 계정, 저장소, 워크플로 파일 이름을 실제 값과 정확히 맞춥니다. GitHub Actions 작업에는 OIDC 토큰을 요청할 수 있는 id-token: write가 필요해요. 이 이름만 보고 모든 저장소 쓰기 권한을 준다고 생각하지 마세요. 저장소 내용 읽기 등 다른 권한과 구분해 필요한 작업에만 둡니다.
특히 새 dist-tag 권한은 기본적으로 꺼져 있어요. 설정을 저장했다고 태그 변경까지 자동으로 허용되는 구조가 아닙니다. 워크플로를 여러 개 연결했다면 어떤 설정이 해당 작업에 권한을 주는지도 목록으로 남겨두세요. 사용하지 않는 오래된 경로가 넓은 권한을 가진 채 남아 있으면 역할을 나눈 효과가 줄어듭니다.
STEP 3후보를 올리고, 같은 결과물을 승격해요
가장 작은 구조는 검증 작업, 후보 게시 작업, 승격 작업의 세 부분이에요. 프로젝트가 작으면 파일까지 세 개로 나눌 필요는 없지만, 각 부분의 입력과 완료 조건은 분리하는 편이 읽기 쉽습니다. 빌드 성공은 패키지 내용이 올바르다는 뜻이 아니므로 실제 묶음의 파일과 소비자 설치까지 확인합니다.
검증 단계에서는 잠금 파일을 사용하는 설치, 정적 검사, 단위 테스트, 패키지 빌드를 수행해요. npm 기반 프로젝트라면 npm ci가 잠금 파일에 맞춘 설치에 적합합니다. package.json과 잠금 파일이 어긋나면 실패하므로 CI가 조용히 의존성을 바꾸는 상황을 줄일 수 있어요. 기존 프로젝트가 다른 패키지 매니저를 쓴다면 그 도구의 동결 설치 방식을 유지하세요. npm ci 문서
빌드·테스트·패키지 파일 확인
커밋·실행 ID·결과물 연결
핵심 사용 예제 실행
변경 후 태그 다시 읽기
진행 순서: 1 → 2 → 3 → 4. 중간 단계가 실패하면 다음 단계로 넘어가지 않습니다.
후보 게시 이후에는 별도 테스트 프로젝트에서 정확한 버전을 설치해 보세요. 예를 들어 문서에 소개한 import 경로가 실제 패키지에서도 존재하는지, 타입 정의가 빠지지 않았는지 확인합니다. 저장소 안에서는 상대 경로로 읽을 수 있었던 파일이 패키지에서는 누락되는 일이 있기 때문이에요. 이 점검은 새로운 버전을 또 만드는 작업이 아니라 배포된 결과물을 소비하는 테스트입니다.
승격 단계의 입력은 자유로운 셸 명령 대신 패키지 이름, 정확한 버전, 허용된 태그로 제한하는 것이 좋아요. 사용자 입력을 명령문에 그대로 이어 붙이지 말고, 허용 목록과 형식을 검증한 뒤 인자로 전달하도록 구현합니다. latest를 옮기기 직전에는 현재 대상과 후보 버전의 검증 기록을 함께 보여주세요.
아래는 전체 워크플로가 아니라 태그 변경의 핵심 명령을 설명하는 예시입니다. 패키지 이름과 버전은 가상 값이며, 실행하면 실제 레지스트리 상태가 바뀌므로 그대로 복사해 운영에 적용하지 마세요.
# 1. 변경 전 기록
npm dist-tag ls @example/widget
# 2. 승인받은 기존 버전을 정식 채널로 지정
npm dist-tag add @example/widget@2.1.0 latest
# 3. 변경 결과를 다시 읽어 확인
npm dist-tag ls @example/widget
승인 장치는 GitHub environment 보호 규칙 등을 검토할 수 있어요. 사용 중인 요금제와 저장소 공개 범위에 따라 가능한 기능이 다를 수 있으니, 설정 화면에서 실제 지원을 확인해야 합니다. 버튼이 있다고 생각하고 설계만 해두면 승인 없이 진행되는 경로를 놓칠 수 있어요. 환경과 배포 보호 규칙
STEP 4실패를 만나면 전체 배포부터 다시 돌리지 않아요
패키지 게시에는 성공했는데 응답을 받기 전에 작업이 끊길 수 있어요. 이때 가장 먼저 할 일은 같은 버전이 이미 존재하는지 확인하는 것입니다. 처음부터 다시 빌드하고 새 버전을 올려 버리면 어떤 결과물을 승인했는지 추적하기 어려워져요. 확인 결과와 로그를 보고 실패한 단계에서 이어갈 수 있게 설계하세요.
태그 변경이 실패한 경우에는 인증 실패인지, 버전이 없는지, 입력이 잘못됐는지 나눠서 봅니다. 권한 오류가 났다고 바로 장기 토큰을 다시 넣는 습관은 피하는 편이 좋아요. runner 종류, npm 버전, 매칭되는 trusted publisher, dist-tag 허용 여부를 순서대로 확인하면 문제 범위가 좁아집니다.
또 하나의 실수는 npm whoami의 성공이나 실패를 OIDC 준비 상태의 최종 판정으로 삼는 거예요. npm 문서는 이 명령을 trusted publishing 권한 확인용으로 사용하지 말라고 설명합니다. 실제로 할 작업과 관련된 확인 절차를 사용해야 해요. 설치용 인증도 게시용 OIDC와 같은 범위라고 가정하면 안 됩니다. 비공개 의존성 설치는 별도 검토 항목입니다.
복구 계획에는 변경 전 태그와 그 버전의 검증 근거를 남겨둡니다. 가령 이전 정상 버전이 2.0.3이었다면 그 값을 복구 대상으로 검토할 수 있어요. 하지만 보안 문제가 생긴 버전을 단순히 오래됐다는 이유로 선택하면 안 되겠죠. 복구는 숫자가 작은 버전을 고르는 일이 아니라 안전하게 사용 가능한 결과물을 다시 지정하는 판단입니다.
동시에 두 개의 승격 작업이 실행되면 뒤늦게 끝난 작업이 태그를 덮어쓸 수도 있어요. 프로젝트별로 승격을 한 번에 하나씩 진행하도록 직렬화하고, 변경 직전의 태그가 예상값과 다르면 멈추도록 정해보세요. 이 검사는 레지스트리 전체를 원자적으로 잠그는 보장은 아니므로, 외부 수동 변경도 같은 운영 절차에 포함해야 합니다.
STEP 5자동화 완료는 결과를 읽어본 뒤에 판단해요
- 사용 중인 러너와 npm 버전이 dist-tag OIDC 요건을 충족한다
- 후보 게시와 승격에 필요한 권한만 명시했다
- 패키지 이름·정확한 버전·태그 입력을 검증한다
- 소비자 설치 테스트와 승인 기록이 같은 버전을 가리킨다
- 변경 전후 태그와 커밋·실행 ID를 기록한다
- 복구 대상과 소비자 측 대응 절차를 확인했다
처음에는 작은 패키지 하나에서 시작하는 편이 좋아요. 정상 경로만 보지 말고 허용되지 않은 워크플로, 잘못된 버전, 승인 취소, 동시에 들어온 두 요청도 점검해 보세요. 위험한 작업이 실패해야 할 때 실제로 멈추는지 확인하는 것이 자동화의 중요한 테스트입니다.
AI에게 작성을 맡긴다면 “npm 배포 만들어줘”보다 현재 환경과 금지할 행동을 함께 주는 것이 도움이 됩니다. 아래 요청문은 기존 설정을 읽고 변경안을 제안하도록 범위를 좁힌 예시예요.
현재 npm 패키지의 릴리스 절차를 검토해줘.
1. Node/npm 버전, runner 종류, 기존 인증 방식을 먼저 확인해줘.
2. 후보 게시와 latest 승격을 나누고 입력 검증을 설계해줘.
3. OIDC dist-tag 지원 요건과 최소 권한을 공식 문서로 확인해줘.
4. 변경 전후 검증, 동시 실행 방지, 실패 단계 재개를 포함해줘.
5. 실제 권한 변경이나 게시 명령은 실행하지 말고 diff와 테스트 계획만 보여줘.
이번 변경의 활용 지점은 릴리스 후반부까지 장기 토큰을 줄일 수 있다는 데 있어요. 그 이점을 살리려면 인증을 바꾸는 작업과 함께, 어떤 버전을 누가 언제 기본 채널로 올릴지 정리해야 합니다. 한 번에 거대한 시스템을 만들기보다 패키지 하나의 후보 게시, 검증, 승격, 확인을 끊김 없이 연결해 보세요.
REFERENCES · 확인한 공식 자료
- 원문 보기
GitHub Changelog · Opt-in dist-tag permissions for npm trusted publishing
dist-tag OIDC 권한은 기본 꺼짐이며 직접 게시 권한과 별도로 지정합니다.
- 원문 보기
npm Docs · Trusted publishing for npm packages
지원 러너, 신뢰 설정, OIDC 권한, dist-tag CLI 버전 요건과 인증 범위를 확인했습니다.
- 원문 보기
npm CLI · npm-dist-tag
태그 조회·변경 명령, latest 기본 동작, 태그 이름 제약을 확인했습니다.
- 원문 보기
GitHub Docs · OpenID Connect
워크플로 신원에 기반한 단기 자격 증명의 개념을 참고했습니다.
- 원문 보기
GitHub Docs · Deployments and environments
환경 보호 규칙, 승인, 배포 브랜치·태그 제한을 검토할 때 사용할 문서입니다.
- 원문 보기
npm CLI · npm-ci
잠금 파일에 따른 깨끗한 설치와 package.json 불일치 시 실패 동작을 참고했습니다.



