깃허브 액션 재사용 가능한 워크플로우와 컴포지트 액션의 차이, workflow_call 선언과 호출 구조, 매트릭스 병렬 테스트 설정, 시크릿 전달 방식(inherit vs 명시), outputs로 잡 간 값 전달하는 법을 실무 기준으로 정리한다. CI/CD, 클라우드, 데브옵스, 개발자, 백엔드, API, 도커, 버전 태그 고정, 보안 감사 대비 체크리스트까지 다룬 깃허브 액션 가이드.
CI 설정 파일이 사이트마다, 서비스마다 따로 붙어 있으면 한 줄 고칠 때마다 열 곳을 다 열어야 한다. 재사용 워크플로우는 별도 파일 하나를 여러 저장소에서 호출하고, 컴포지트 액션은 반복되는 스텝 묶음을 하나의 단위로 만든다. 어느 걸 먼저 쓸지 고르는 기준은 단순하다. 저장소가 여러 개이면서 파이프라인 흐름 자체를 공유하고 싶으면 재사용 워크플로우, 한 저장소 안에서 스텝을 줄이고 싶으면 컴포지트 액션이다.
배포 파이프라인을 직접 짜면서 깃허브 코파일럿과 클로드 코드로 YAML을 자동 보완하는 흐름, 트랜잭션 아웃박스처럼 배포와 연결된 백엔드 패턴을 같이 보면 파이프라인 설계의 빈칸이 줄어든다.
재사용 워크플로우와 컴포지트 액션 | 어느 걸 먼저 볼까
깃허브 액션에는 중복을 없애는 방법이 세 가지다. 재사용 가능한 워크플로우(Reusable Workflow), 컴포지트 액션(Composite Action), 그리고 같은 저장소 안에서만 쓰는 로컬 컴포지트다. 세 가지는 쓰는 자리가 다르다.
방법
단위
공유 범위
호출 방법
재사용 워크플로우
잡 전체 흐름
저장소 간
uses: org/repo/.github/workflows/ci.yml@main
컴포지트 액션
스텝 묶음
저장소 간 (공개/내부)
uses: org/actions-repo/setup-node@v2
로컬 컴포지트
스텝 묶음
현재 저장소만
uses: ./.github/actions/my-action
재사용 워크플로우는 on: workflow_call을 선언한 파일을 다른 저장소의 워크플로우가 uses:로 호출하는 구조다. 호출자는 with:로 인풋을, secrets: inherit로 시크릿을 넘길 수 있다. 컴포지트 액션은 action.yml 파일 하나에 여러 스텝을 묶고, 마켓플레이스에 올리거나 비공개 저장소에서 내부용으로 쓴다.
어느 쪽이든 버전 태그를 달아 두는 편이 안전하다. @main으로 고정하면 최신 커밋이 모든 호출자에 즉시 영향을 주어 디버깅이 어려워진다.
재사용 워크플로우 파일 구조 | 선언부터 호출까지
재사용 워크플로우는 .github/workflows/ 아래에 일반 워크플로우와 같은 위치에 둔다. 차이는 트리거가 on: workflow_call이라는 점이다. 인풋과 시크릿은 선언해야 호출자가 넘길 수 있다.
on:
push:
branches: [main]
jobs:
deploy-prod:
uses: org/shared-workflows/.github/workflows/deploy-node.yml@v1.2.0
with:
node-version: '20'
environment: production
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
시크릿 전달 주의: 호출자가 secrets: inherit를 쓰면 자기 저장소의 모든 시크릿이 재사용 워크플로우에 그대로 넘어간다. 필요한 시크릿만 명시적으로 나열하는 편이 보안 감사에서 깔끔하다. secrets: inherit는 편하지만 어느 시크릿이 어디로 가는지 파악하기 어렵다.
컴포지트 액션 만드는 순서 | action.yml 부터 배포까지
컴포지트 액션은 저장소의 루트 또는 특정 디렉터리에 action.yml 하나를 두면 된다. 같은 저장소 안에서만 쓸 로컬 컴포지트라면 .github/actions/{이름}/action.yml에 두고, 여러 저장소에 공유할 거라면 별도 저장소를 만들어 버전 태그를 단다.
컴포지트 액션은 run: 스텝에 shell: bash를 반드시 명시해야 한다. 재사용 워크플로우는 잡 단위라 러너가 명확하지만, 컴포지트는 호출자의 러너에서 돌기 때문에 셸을 직접 지정해야 플랫폼 간 일관성이 생긴다.
공유용 저장소를 따로 만들 때는 action.yml이 루트에 있어야 uses: org/actions-repo@v1로 호출된다. 하위 디렉터리에 두려면 uses: org/actions-repo/subdir@v1처럼 경로를 붙인다. 하나의 저장소에 액션 여러 개를 두는 멀티 액션 저장소 패턴이다.
매트릭스 전략으로 병렬 테스트 | 노드 버전·OS 조합 세팅
여러 환경에서 동시에 테스트를 돌리고 싶으면 strategy.matrix를 쓴다. 재사용 워크플로우와 함께 쓸 때는 호출자에서 매트릭스를 선언하고, 인풋으로 넘기는 방식이 깔끔하다.
.github/workflows/test.yml (매트릭스 + 재사용 워크플로우 조합)
on:
pull_request:
branches: [main, develop]
jobs:
# 매트릭스는 호출자에서 선언
test:
strategy:
fail-fast: false
matrix:
node: ['18', '20', '22']
os: [ubuntu-latest, macos-latest]
uses: org/shared-workflows/.github/workflows/test-node.yml@v2.0.0
with:
node-version: ${{ matrix.node }}
os: ${{ matrix.os }}
secrets: inherit
# 매트릭스 결과를 한 곳에서 확인하는 체크
test-result:
needs: test
runs-on: ubuntu-latest
if: always()
steps:
- name: Check test results
run: |
if [[ "${{ needs.test.result }}" != "success" ]]; then
echo "테스트 실패"
exit 1
fi
fail-fast: false 권장: 기본값(true)이면 매트릭스 중 하나가 실패할 때 나머지 잡이 모두 취소된다. 어느 환경에서 깨지는지 전부 보고 싶을 때는 fail-fast: false로 바꾼다. 다만 그만큼 빌드 시간과 사용 분(minute)이 늘어난다. 무료 티어 저장소에서는 매트릭스 크기를 조심한다.
시크릿과 환경변수를 워크플로우 간에 넘기는 법
재사용 워크플로우는 호출자의 시크릿을 자동으로 보지 못한다. 두 가지 방법이 있다.
방법
코드
특징
명시 전달
secrets: MY_SECRET: ${{ secrets.MY_SECRET }}
감사 추적 명확, 필요한 것만
inherit
secrets: inherit
편리하지만 전체 노출
환경 변수는 env:로 잡 레벨에서 선언하거나, 스텝 레벨에서 env:로 내린다. 재사용 워크플로우로 넘길 때는 인풋(with:)을 쓰되, 민감 값은 반드시 시크릿으로 분리한다. 인풋은 로그에 노출될 수 있다.
재사용 워크플로우와 컴포지트 액션은 쓰기 시작하면 모든 호출자에 영향을 준다. 배포 전에 아래 항목을 한 번 더 확인한다.
자주 묻는 것들 | FAQ
재사용 워크플로우를 private 저장소에 두면 다른 저장소에서 호출할 수 있나?
깃허브 엔터프라이즈 또는 깃허브 팀 플랜에서는 같은 조직 내 저장소끼리 internal/private 재사용 워크플로우를 공유할 수 있다. 무료 플랜에서 private 저장소의 재사용 워크플로우는 같은 저장소에서만 호출 가능하다. 조직 단위로 공유하려면 저장소 설정에서 "Allow access from repositories in this organization" 권한을 켜야 한다.
컴포지트 액션에서 도커 컨테이너를 쓸 수 있나?
컴포지트 액션은 using: 'composite'로 선언되어 호출자의 러너 환경에서 실행된다. 도커 컨테이너 기반 액션(using: 'docker')과는 별개다. 컴포지트 안에서 docker run 명령을 스텝으로 실행할 수는 있지만, 액션 자체가 컨테이너로 격리되지는 않는다. 완전한 컨테이너 격리가 필요하면 도커 액션으로 별도 정의한다.
재사용 워크플로우를 호출하는 잡에서 if 조건을 걸 수 있나?
가능하다. 호출자 쪽의 잡에 if: github.ref == 'refs/heads/main'처럼 조건을 달면, 조건이 맞을 때만 재사용 워크플로우가 실행된다. 재사용 워크플로우 파일 안의 잡에도 if:를 달 수 있다. 다만 재사용 워크플로우의 if:는 넘어온 인풋 기준이고, 호출자의 github.event 같은 외부 컨텍스트는 직접 참조가 안 된다.
컴포지트 액션에서 Node.js 스크립트를 실행하려면 어떻게 하나?
runs.steps에 run: node script.js를 넣고 shell: bash를 지정하면 된다. 단, 해당 Node.js 버전이 러너에 이미 설치되어 있어야 한다. 버전을 직접 관리하려면 컴포지트 안에서 uses: actions/setup-node@v4를 먼저 호출한다. using: 'node20'으로 선언하면 직접 JS 파일을 진입점으로 쓸 수 있지만, 그러면 컴포지트가 아닌 자바스크립트 액션이 된다.
재사용 워크플로우가 실패했을 때 호출자에서 원인을 어떻게 보나?
깃허브 액션 UI에서 호출자 워크플로우 실행 화면을 열면, 재사용 워크플로우는 별도 잡 패널로 펼쳐진다. 각 스텝 로그를 클릭해 오류 메시지를 볼 수 있다. 재사용 워크플로우 저장소가 private이어도 호출자 저장소에서 로그를 열 수 있다. 단, ACTIONS_RUNNER_DEBUG=true 시크릿을 호출자 저장소에 설정해야 디버그 로그가 보인다.
재사용 워크플로우를 중첩해서 호출할 수 있나?
깃허브는 최대 4단계 중첩을 허용한다(호출자 포함 최대 20개 워크플로우). 재사용 워크플로우 안에서 또 다른 재사용 워크플로우를 uses:로 부를 수 있다. 다만 중첩이 깊어지면 실패 원인 추적이 어려워진다. 실무에서는 2단계 이상 중첩을 피하고, 공통 로직은 컴포지트 액션으로 분리하는 편이 낫다.