워크플로 파일을 바꾸지 않았는데 CI가 다른 액션 코드를 실행할 수 있다. 태그와 브랜치가 이동하기 때문이다. GitHub Actions의 잠금 파일은 이 참조를 커밋에 연결하지만, 다른 저장소의 재사용 워크플로와 실행 중 만들어지는 로컬 액션까지 한 파일로 고정하지는 않는다. 팀의 공통 CI를 먼저 정리하고 앱 저장소로 확장하는 전환 사례로 적용 범위를 살펴본다.
앱 저장소에서 공통 빌드 워크플로를 호출하고, 그 안에서 여러 액션을 쓰는 팀을 생각해 보자. 앱 코드와 CI YAML을 그대로 두어도 액션의 v3 태그가 이동하면 실행 코드가 달라질 수 있다. 반대로 모든 참조를 오래된 SHA에 고정한 채 갱신하지 않으면 필요한 수정도 들어오지 않는다. 팀이 관리할 대상은 고정한 코드와 다음 변경을 승인하는 과정이다.
GitHub는 2026년 10월 28일 Actions workflow lockfiles의 정식 제공을 발표했다. Free·Team·Enterprise Cloud와 데이터 레지던시 환경을 명시한다. 사람이 읽는 태그를 유지하면서 액션의 실제 실행 커밋을 잠금 파일에 기록하고, 이미 등록한 워크플로의 Dependabot 버전 변경에는 대응하는 잠금 항목도 함께 갱신한다.
10월 31일 확인한 how-to 상단에는 여전히 Enterprise Cloud만 표시돼 있어 GA 발표와 범위 표기가 다르다. 개별 계정의 제공 화면은 이 글에서 확인하지 않았다. 또한 정책의 Evaluate 모드는 Enterprise Cloud에 한정되며, 공식 확장 README는 GitHub Enterprise Server를 지원하지 않는다고 명시한다. 클라우드 GA를 자체 호스팅 서버의 지원으로 확대하면 안 된다.
이 글은 공식 문헌과 gh-actions-lock 커밋 113f91d77e0af03838b6ae3bef0e038cbab4c696을 바탕으로 한 분석이다. 아래 팀·저장소·오류 대응 사례는 자체 설계했으며, 확장 설치나 GitHub 워크플로 실행 결과가 아니다.
무엇을 고정하는가: 이름에서 실행 코드로
개념 문서에 따르면 .github/workflows/actions.lock에는 워크플로별 의존성과 각 참조가 가리키는 커밋이 들어간다. 저장소 이름 외에 owner_id·repo_id도 기록해 같은 이름 뒤의 저장소가 바뀌었는지 확인한다. 파일은 생성기로 관리하며 손으로 고치지 않는다.
등록한 워크플로는 필요한 액션 항목을 갖춰야 한다. 실행에는 기록된 커밋을 사용하며, 그 커밋이 액션의 원 저장소에서 해당 참조의 이력으로 도달 가능한지도 검사한다. 따라서 SHA 문자열을 적었다는 이유만으로 출처 확인이 끝나는 것은 아니다.
여기서 전이 의존성은 액션 A가 내부에서 사용하는 액션 B다. A만 직접 SHA로 고정해도 A 내부의 B가 이동하는 태그를 참조한다면 별도 변동 지점이 남을 수 있다. 잠금 파일은 이 연결을 다룬다. 다만 jobs.<job_id>.uses로 호출하는 재사용 워크플로는 다른 경계다. 호출 자체는 전체 커밋 SHA로 고정하고, 그 워크플로 안의 액션은 정의한 저장소의 잠금 파일로 관리한다.
위 그림은 자체 제작한 설명도다. 앱 저장소의 잠금 파일을 공통 CI 저장소로 복사하는 구조가 아니다. 저장소별로 파일을 생성하고 변경 책임을 나누는 구조다.
고정 범위 밖의 입력도 남는다. 실행 파라미터·비밀 값·러너 이미지까지 동일해지는 기능은 아니며, README는 컨테이너 이미지도 잠금 대상이 아니라고 설명한다. 따라서 같은 액션 커밋으로 실행했다는 사실을 같은 빌드 산출물이나 코드의 안전성으로 바꾸어 말할 수 없다.
두 저장소를 쓰는 팀의 전환 계획
가상의 팀에는 앱 저장소 invoice-app과 공통 CI 저장소 team-ci가 있다. 앱의 테스트 작업은 외부 액션을 직접 쓰고, 배포 작업은 team-ci의 재사용 워크플로를 호출한다. 공통 워크플로는 패키징 액션을 실행한다. 앱에는 체크아웃한 파일을 읽는 ./.github/actions/check-report도 있다.
이 팀의 목표는 “잠금 파일 한 개 생성”이 아니다. 테스트와 배포 각각에서 누가 승인한 액션 코드가 실행되는지 설명할 수 있어야 한다. 다음은 이 목표에 맞춘 편집 설계다.
| 변경 묶음 | 남길 결과 | 다음 단계로 갈 조건 |
|---|---|---|
| 공통 CI 저장소 | 공유 워크플로의 잠금 파일과 액션 변경 내역 | 공통 워크플로 담당자가 전이 의존성까지 검토하고 시험 실행을 확인 |
| 앱 저장소 | 직접 사용하는 액션의 잠금 파일, 공통 워크플로를 가리키는 전체 SHA | 검토한 공통 CI 커밋을 정확히 참조하고 앱 테스트·배포 시험이 각각 통과 |
| 조직 적용 | 대상 저장소·워크플로 목록과 신규 파일 등록 절차 | 새 워크플로가 생겨도 잠금 등록·검증·검토 담당자가 빠지지 않음 |
순서를 거꾸로 잡으면 앱 담당자는 자기 파일을 모두 생성했는데도 공통 워크플로 쪽의 등록이 빠진 채 도입을 끝냈다고 생각하기 쉽다. 정책 문서는 호출받는 워크플로가 원 저장소에 등록되지 않았으면 Require lockfile 정책이 호출 측 실행도 막는다고 설명한다. 그래서 공통 저장소를 먼저 전환하는 편이 이 사례에 맞다.
실제 적용에서는 배포 자격증명이 없는 시험 경로부터 확인하고, 결과를 확인한 뒤 배포 경로로 넓히는 것을 권한다. 이것은 글에서 제안하는 진행 순서이며 제품이 시험 환경을 자동 생성해 준다는 뜻은 아니다. 외부 조직이 관리하는 공유 워크플로라면 상대 저장소의 전환을 강제할 수 없다. 전체 SHA 호출과 내부 의존성 관리 상태를 확인하고, 해결되지 않은 공백을 남긴 채 조직 전체 도입 완료로 보고하지 않는다.
가장 먼저 확인할 반례: 로컬 액션의 파일은 어디서 오는가
로컬 경로 ./는 러너의 작업 공간을 기준으로 한다. 반면 같은 저장소를 나타내는 $/ 경로는 워크플로 자체의 커밋을 기준으로 한다. 도입 문서는 전환 옵션 --migrate-local-actions가 이 참조를 바꾼다고 설명한다.
우리 사례의 check-report가 저장소에 커밋된 파일이고 그 버전을 실행하려는 목적이라면 전환이 자연스럽다. 그러나 앞선 단계에서 이 액션 파일을 생성하거나, 별도 커밋을 체크아웃한 뒤 그 안의 액션을 실행하도록 설계했다면 이야기가 달라진다. 워크플로 커밋의 파일은 실행 시 작업 공간의 파일과 같다는 보장이 없다. 경로 한 글자의 변경이 실행할 코드의 출처를 바꾸는 셈이다. 이것이 자동 치환 결과를 검토해야 하는 구체적인 이유다.
원래 ./ 액션이 다른 액션을 부르면 생성기는 local-action 오류로 쓰기를 중단할 수 있다. --ignore-local-actions는 그 공백을 잠금으로 채우는 옵션이 아니다. 내부 액션에 같은 워크플로의 잠금 항목이 없으면 실행이 해당 단계에서 실패할 수 있으며, 앞 단계는 이미 실행됐을 수 있다. 무시 옵션을 적용해 파일 생성에 성공한 것과 업무가 안전하게 끝난 것은 별도로 확인한다.
우리 팀은 다음 두 갈래로 판단할 수 있다. 커밋된 액션을 쓰려던 것이라면 $/ 전환 diff와 시험 결과를 함께 검토한다. 실행 중 만들어지는 코드를 의도했다면 먼저 해당 생성·체크아웃 의존성을 문서화하고 구조를 조정한다. 오류를 지우기 위해 실행 의미를 바꾸지 않는다.
생성과 검증, 갱신은 다른 작업이다
다음은 공식 명령을 바탕으로 한 미실행 적용 예시다. 권한이 있는 별도 작업 브랜치와 인증된 GitHub CLI 환경에서 사용하며, 이 글에는 성공 출력을 만들어 넣지 않았다.
# 확장 설치
gh extension install github/gh-actions-lock
# 한 워크플로부터 도입
gh actions-lock .github/workflows/ci.yml
# 생성된 잠금 파일과 워크플로 변경을 함께 검토
git diff -- .github/workflows/
# 로컬 항목 포함 여부 확인: 네트워크 없이 검사
gh actions-lock --verify-local
# 원격 출처 검사까지 수행: 파일은 수정하지 않음
gh actions-lock --verify
.github/workflows/ci.yml은 자신의 파일 경로로 바꾼다. --verify-local과 --verify의 범위는 같지 않다. 전자는 오프라인 포함 관계 검사이고, 후자는 네트워크를 사용하는 출처 검사까지 포함한다. 오프라인 통과를 원격 이력 확인으로 기록하지 않는다.
생성기는 잠금 파일만 추가하는 것이 아니라 버전 참조를 더 구체적인 태그로 바꿀 수 있다. 그래서 actions.lock만 따로 승인하지 않고 워크플로 diff를 같이 읽는다. 기존 허용 목록을 태그로만 작성한 조직도 주의해야 한다. 도입 후 정책은 잠근 SHA를 검사하므로 기존 태그 항목이 맞지 않을 수 있다. 이를 해결하려고 허용 대상을 무조건 넓히기보다 검토한 전체 SHA와 조직 정책의 의도를 먼저 대조한다.
Dependabot은 등록된 워크플로의 버전 변경을 유지하는 데 도움이 된다. 아직 등록되지 않은 신규 워크플로를 자동으로 온보딩해 주는 기능으로 간주하면 안 된다. 새 파일 추가 시 생성·검증·리뷰를 수행하는 절차는 따로 필요하다.
CI가 멈췄을 때: 어떤 변경을 승인할 것인가
고정 커밋의 문제 해결 문서를 바탕으로, 우리 팀이 받을 질문을 세 가지로 나눴다. 아래는 문헌 기반 대응표이며 오류 재현 로그가 아니다.
| 상황 | 먼저 구분할 것 | 우리 팀의 다음 행동 |
|---|---|---|
uses:를 수정했는데 항목이 맞지 않음 |
의도한 의존성 변경인가, 잘못 바꾼 줄인가 | 의도한 변경이면 생성 후 diff 검토. 오타라면 워크플로 변경을 되돌림 |
main·부분 버전 태그가 정상적으로 전진함 |
기존 커밋을 유지할지 새 코드를 채택할지 | 변경 이력을 읽고 필요할 때 --relock. 자동으로 모든 참조를 최신화하지 않음 |
| 잠근 커밋이 upstream 참조 이력에서 사라짐 | upstream 이력 재작성, 참조 이동, 수동 파일 변경 중 무엇인가 | 원본 잠금 파일·릴리스·이력을 대조. 원인 조사 전 자동 수락 중단 |
부분 버전과 정확한 버전 태그도 구분해야 한다. v3 같은 부분 버전의 이동은 기존 핀을 유지할 수 있지만 v3.1.0 같은 정확한 태그의 불일치는 오류로 처리된다. 고정된 CLI 문서에서 부분 버전의 ref-moved 경고는 JSON 출력에만 나타나며 종료 코드를 바꾸지 않는다고 설명한다. 따라서 “명령이 0으로 끝났다”는 사실만으로 검토할 upstream 변화가 없다고 해석하지 않는다.
이력 단절에는 --relock만 반복해도 해결되지 않는 경우가 있다. --accept-moved는 이력을 확인한 사람이 새 커밋을 신뢰한다는 결정을 반영하는 옵션이다. 공식 문서는 자동화에서 사용하지 말라고 명시하며, 복합 액션 내부의 문제라면 그 액션을 업그레이드하거나 검증 가능한 것으로 교체하는 등 다른 조치가 필요하다고 설명한다.
여기에 우리 팀의 복구 기준을 하나 더 둔다. 실수로 잠금 파일을 바꿨다면 이전 승인본과 워크플로를 일관되게 복원하고 검증한다. 그러나 upstream 이력 자체가 바뀐 상황에서는 옛 파일 복원만으로 같은 커밋의 출처 조건이 회복되지 않는다. 그때는 “지난주 파일이니 안전하다”는 판단 대신 검증 가능한 의존성 버전을 다시 선정해야 한다. 복구는 파일 복사와 실행 조건 확인을 함께 끝내는 작업이다.
도입의 완료 조건을 기록으로 남긴다
두 저장소 도입·복구 기록지는 자체 제작한 빈 양식이다. 앱과 공통 CI의 담당자·커밋·잠금 파일을 나누고, 로컬 액션의 파일 출처, 원격 검증 결과, 시험 실행 URL과 미확인 항목을 적게 했다. 실제 실행 전에는 결과란을 unknown으로 둔다.
이 팀이 도입을 마쳤다고 판단할 시점은 파일이 생긴 날이 아니라 다음 변경을 처리할 수 있을 때다. 예를 들어 공통 CI에서 액션을 추가했을 때 같은 변경 묶음으로 잠금 항목이 갱신되는지, 앱이 승인한 공통 커밋을 가리키는지, 누가 새 SHA의 코드를 읽는지 확인한다. Require lockfile을 적용한다면 의존성 없는 대상 워크플로까지 등록 여부를 확인해야 하며, Evaluate를 사용할 수 없는 요금제에는 동일한 사전 관측 화면이 있다고 가정하지 않는다.
모든 팀이 지금 전체 저장소를 전환할 필요는 없다. 동적으로 생성하는 로컬 액션이 많거나 공유 워크플로의 변경 권한이 없다면, 현재의 전체 SHA 고정과 갱신 절차를 유지하면서 전환 가능한 경로부터 검토할 수 있다. 잠금 파일의 이점은 읽기 쉬운 참조·전이 의존성 관리·검토 가능한 변경 기록이다. 그 이점을 얻으려면 제외되는 경로와 갱신 책임도 함께 정해야 한다.
액션 코드의 고정에서 실제 배포 산출물의 증거까지 이어 보고 싶다면 SBOM·provenance·VEX로 배포를 판단하는 법이 다음 읽을 거리다. 여러 에이전트가 변경 브랜치를 나누는 팀이라면 GitHub의 ref 단위 처리와 남는 충돌을 함께 볼 수 있다. 이 글을 저장해 두고 다음 CI 의존성 변경 때 기록지를 다시 채워 보면, 도입 당시 결정과 현재 조건의 차이를 찾기 쉽다.
- 액션의 직접·전이 의존성은 잠금 파일로 고정하지만, 재사용 워크플로 호출은 전체 커밋 SHA와 원 저장소의 잠금 파일을 따로 확인한다.
- 로컬 ./ 경로를 $/ 경로로 바꾸면 실행할 파일의 기준이 달라진다. 자동 생성이나 다른 체크아웃에 의존하는 액션은 전환 전에 동작을 검토한다.
- 누락된 항목, 정상적인 태그 전진, upstream 이력 단절은 서로 다른 문제다. 오류를 만났다고 잠금 파일을 무조건 최신으로 다시 만들지 않는다.
근거와 원자료
자료의 발행 시점과 이번 글에서 참고한 범위를 함께 기록합니다.
- GitHub Actions workflow lockfiles GA ↗GitHub · 2026
2026-10-28 GA·제공요금제·Dependabot. 안전성 보증 아님.
- Workflow dependency lockfiles ↗GitHub · 2026
액션 의존성 고정·원저장소 도달 가능성·재사용 workflow 경계.
- Locking action dependencies ↗GitHub · 2026
단일 workflow 도입·로컬 액션 경로·allowlist·workflow 변경. 상단 Enterprise Cloud 표기는 GA 발표 범위와 다름.
- Enforcing workflow lockfiles ↗GitHub · 2026
Require lockfile·Evaluate Enterprise Cloud 한정·공유 workflow 원저장소 도입.
- gh-actions-lock README — pinned commit ↗GitHub · 2026
커밋113f91d77e0af03838b6ae3bef0e038cbab4c696. 명령/모드·오프라인 범위·GHES 제외·컨테이너 미고정. 직접 실행 미실시.
- gh-actions-lock troubleshooting — pinned commit ↗GitHub · 2026
동일 고정 커밋. 오류 종류별 복구와 ref-moved 경고·unreachable-pin. 직접 재현 아님.
갱신 기록
GA 발표와 공식 문서·고정 커밋을 대조하고 두 저장소의 도입 사례 및 복구 판단표를 작성했다. GitHub 실행은 미실시했다.
오류를 발견했다면 댓글로 알려 주세요 →
이 글에 댓글 남기기
댓글 공개 방식을 확인하고 있습니다. 개인정보나 비밀 값은 적지 마세요.
댓글을 불러오는 중입니다.
내 댓글 확인·수정·삭제
등록 후 안내된 댓글 번호와 비밀번호를 입력하세요. 승인 대기 중인 댓글도 확인할 수 있습니다.