GitHub Actions Composite Action 개발: action.yml부터 Marketplace 배포까지 실전 가이드

화면에 떠 있는 15번째 workflow 로그 오류를 보고 있습니다. 원인은 무엇일까요? 어느 private repository의 npm install 단계에서 NODE_AUTH_TOKEN을 빠뜨린 것입니다.
이번 주에만 세 번째입니다. 저장소가 8개 있고, 각 저장소마다 거의 같은 workflow 설정을 복사해 붙여 넣었습니다. checkout, setup-node, install, build, test. 한 곳을 바꾸면 8개 저장소에 모두 반영해야 합니다. 어느 날 Node 버전을 올리면서 5개 저장소만 수정했고, 나머지 3개는 잊어버렸습니다.
그 순간 깨달았습니다. 이 방식은 더 이상 지속할 수 없습니다. GitHub Actions의 Composite Action은 바로 이런 문제를 해결하기 위해 있습니다. 반복되는 단계를 재사용 가능한 컴포넌트로 패키징하고, 함수를 호출하듯 여러 workflow에서 사용할 수 있습니다. 이 글에서는 첫 Composite Action 개발부터 Marketplace 배포까지 따라가며 CI/CD 컴포넌트화의 핵심을 정리합니다.
1장: Composite Action 핵심 개념
1.1 Composite Action이란?
Composite Action은 GitHub Actions가 제공하는 컴포넌트화 메커니즘입니다. 여러 steps를 하나의 독립적인 Action으로 감싸서 서로 다른 workflow에서 재사용할 수 있게 합니다.
JavaScript Action이나 Docker Action과 달리 Composite Action은 코드를 작성할 필요가 없습니다. YAML 설정만 작성해 일련의 단계를 정의하면 GitHub가 자동으로 실행합니다. 간단히 말하면 Composite Action은 여러 단계를 묶은 “함수 래핑”입니다.
공식 정의는 이렇습니다. Composite Action은 runs.using: "composite"으로 식별되며, 모든 단계는 같은 Runner 안에서 실행됩니다. 따라서 작업 디렉터리와 환경 변수에 직접 접근할 수 있고, 다른 Action도 호출할 수 있습니다.
1.2 세 가지 Action 타입 비교
GitHub Actions는 세 가지 Action 타입을 지원합니다.
| 타입 | 구현 방식 | 적합한 상황 |
|---|---|---|
| JavaScript Action | JS/TS 코드 작성 | 복잡한 로직이나 API 호출이 필요할 때 |
| Docker Action | Dockerfile 작성 | 특정 환경이나 의존성이 필요할 때 |
| Composite Action | 순수 YAML 설정 | 기존 단계를 조합하고 빠르게 재사용할 때 |
Composite Action의 장점은 분명합니다. 코드가 필요 없고, 빠르게 개발할 수 있으며, 유지보수가 쉽습니다. 패키징, 컴파일, 의존성 관리까지 처리할 필요 없이 YAML만 작성하면 됩니다.
1.3 Composite Action vs reusable workflow
많은 사람이 헷갈리는 지점입니다. 겉으로 보면 둘 다 “재사용”이지만 본질은 다릅니다.
Composite Action은 단계 묶음입니다. Job 안에서 실행되고 호출자의 Runner를 사용하며, Secrets를 명시적으로 전달해야 합니다.
reusable workflow는 완전한 파이프라인입니다. 독립적인 Job을 만들 수 있고, 자체 Runner를 둘 수 있으며, Secrets를 자동으로 상속할 수 있습니다.
예를 들어 “의존성 설치 + 테스트 실행”을 묶어 재사용하고 싶다면 Composite Action이 맞습니다. 전체 “빌드→테스트→배포” 파이프라인을 표준화하고 싶다면 reusable workflow가 맞습니다. 4장에서 더 자세히 비교합니다.
2장: 첫 Composite Action 개발하기
2.1 action.yml 구조 자세히 보기
Composite Action의 핵심은 action.yml 파일입니다. 이 메타데이터 파일은 Action의 이름, 입력, 출력, 실행 단계를 정의합니다.
먼저 가장 작은 예제를 보겠습니다.
name: 'Hello World'
description: 'A simple composite action'
runs:
using: "composite"
steps:
- run: echo "Hello from composite action!"
shell: bash
이 Action은 한 가지 일만 합니다. 한 줄의 문자를 출력합니다. 하지만 실제 프로젝트에서는 파라미터화와 출력이 필요합니다.
2.2 완전한 action.yml 예제
아래는 Node.js 프로젝트의 빌드와 테스트에 바로 사용할 수 있는 Composite Action 예제입니다.
name: 'Build and Test'
description: 'Install dependencies, build project, and run tests'
author: 'Your Name'
inputs:
node-version:
description: 'Node.js version to use'
required: true
default: '20'
install-command:
description: 'Command to install dependencies'
required: false
default: 'npm ci'
build-command:
description: 'Command to build the project'
required: false
default: 'npm run build'
test-command:
description: 'Command to run tests'
required: false
default: 'npm test'
outputs:
build-path:
description: 'Path to the build output'
value: ${{ steps.build.outputs.path }}
test-coverage:
description: 'Test coverage percentage'
value: ${{ steps.coverage.outputs.value }}
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
run: ${{ inputs.install-command }}
shell: bash
- name: Build project
id: build
run: |
${{ inputs.build-command }}
echo "path=dist" >> $GITHUB_OUTPUT
shell: bash
- name: Run tests
id: coverage
run: |
${{ inputs.test-command }}
echo "value=85" >> $GITHUB_OUTPUT
shell: bash
2.3 필드 자세히 보기
inputs: Action이 받는 파라미터를 정의합니다. 각 파라미터에는 다음을 지정할 수 있습니다.
description: 파라미터 설명입니다. Marketplace에 표시됩니다.required: 필수 여부입니다.default: 기본값입니다.
outputs: Action의 출력을 정의합니다. $GITHUB_OUTPUT 환경 변수로 값을 전달합니다. 형식에 주의하세요.
echo "name=value" >> $GITHUB_OUTPUT
runs.steps: 실행 단계입니다. 일반 workflow와 비슷하지만 두 가지 중요한 차이가 있습니다.
-
shell을 반드시 명시해야 합니다. 각
run명령에는shell: bash또는sh,pwsh가 필요합니다. Composite Action의 강제 요구사항입니다. -
uses로 다른 Action을 호출할 수 있습니다. 위 예제의actions/setup-node@v4처럼 사용할 수 있습니다.
2.4 자주 만나는 함정
개발하면서 몇 가지 함정을 밟았습니다.
함정 1: inputs에는 type 필드가 없습니다
Composite Action의 inputs는 string 타입만 지원합니다. reusable workflow처럼 type: boolean이나 type: number를 정의할 수 없습니다. 불리언 값이 필요하다면 "true" 또는 "false" 문자열을 넘기고, 단계 안에서 판단해야 합니다.
함정 2: shell은 반드시 명시해야 합니다
일반 workflow에서는 run 명령에서 shell을 생략할 수 있고 GitHub가 Runner에 따라 자동으로 선택합니다. 하지만 Composite Action에서는 반드시 명시해야 합니다. 그렇지 않으면 오류가 납니다.
함정 3: outputs는 step id로 참조해야 합니다
출력 값의 value 필드는 단계의 출력을 참조해야 합니다.
outputs:
my-output:
value: ${{ steps.my-step.outputs.result }}
그리고 해당 단계에는 반드시 id를 설정해야 합니다.
- id: my-step
run: echo "result=hello" >> $GITHUB_OUTPUT
3장: Composite Action 사용하기
3.1 로컬 참조
가장 간단한 사용 방식은 같은 저장소 안에서 참조하는 것입니다. Action이 .github/actions/build-test/action.yml에 있다고 가정해 보겠습니다.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 로컬 참조
- uses: ./.github/actions/build-test
with:
node-version: '20'
test-command: 'npm run test:ci'
경로는 ./로 시작하며 저장소 루트 기준입니다. 이 방식은 팀 내부 재사용에 적합하며 Marketplace에 배포할 필요가 없습니다.
3.2 저장소 간 참조
여러 저장소에서 공유하고 싶다면 Action을 독립 저장소에 두고 저장소 간에 참조할 수 있습니다.
steps:
# 조직 내부 공유 Action 참조
- uses: your-org/shared-actions/build@v1
with:
node-version: '18'
경로 형식은 owner/repo/path@version입니다. 여기서 path는 저장소 안에서 Action이 위치한 상대 경로입니다.
권장하는 조직 단위 Action 저장소 설계:
shared-actions/
├── build/
│ └── action.yml # 빌드 관련
├── deploy/
│ └── action.yml # 배포 관련
├── lint/
│ └── action.yml # 코드 검사
└── README.md
이렇게 구성하면 참조도 명확해집니다.
your-org/shared-actions/build@v1your-org/shared-actions/deploy@v1
3.3 Marketplace에서 사용하기
Marketplace에 배포한 뒤에는 사용자가 직접 검색하고 참조할 수 있습니다.
steps:
# Action이 "build-test-action" 이름으로 배포되었다고 가정
- uses: your-org/[email protected]
with:
node-version: '20'
Marketplace는 버전 선택, 사용 통계, README 표시 같은 기능을 제공합니다. 오픈소스 프로젝트나 공개 공유용 Action에 적합합니다.
3.4 버전 선택 전략
Action을 참조할 때는 세 가지 버전 선택 방식이 있습니다.
# 방식 1: commit SHA(가장 안전하고 변경 불가)
- uses: your-org/action@a1b2c3d4e5f6...
# 방식 2: semantic version tag(권장)
- uses: your-org/[email protected]
# 방식 3: major tag(최신 v1.x 자동 추적)
- uses: your-org/action@v1
# 권장하지 않음: latest tag(예기치 않게 업그레이드될 수 있음)
# - uses: your-org/action@latest
보안 권장사항:
프로덕션 환경에서는 commit SHA를 사용하는 것이 가장 안전합니다. SHA는 변경되지 않으므로 관리자가 tag를 갱신해도 예기치 않게 업그레이드되지 않습니다.
내부 프로젝트에서는 major tag(예: @v1)가 유연한 선택입니다. 최신 v1.x 버전을 자동으로 추적해 bug fix와 새 기능을 받을 수 있습니다.
@latest는 피하세요. 예기치 않은 업그레이드로 빌드가 깨질 수 있습니다.
4장: Composite Action vs reusable workflow
4.1 기능 비교표
선택할 때 참고할 핵심 기준입니다.
| 기준 | Composite Action | reusable workflow |
|---|---|---|
| 실행 계층 | Job 안의 단계 묶음 | 독립 Job |
| Runner | 호출자의 Runner 사용 | 새 Runner 생성 또는 지정 |
| Secrets | 명시적 전달 필요 | 자동 상속 |
| 입력 타입 | string만 지원 | boolean / number / string |
| 출력 전달 | $GITHUB_OUTPUT | outputs + workflow_call |
| 동시성 제어 | 호출자의 동시성 제한 상속 | 독립적으로 설정 가능 |
| 환경 변수 | 상속 + 추가 가능 | 독립 scope |
| 적합한 상황 | 단일 기능 래핑 | 전체 파이프라인 표준화 |
4.2 선택 의사결정 매트릭스
상황에 따라 적절한 재사용 방식을 선택하세요.
Composite Action을 쓰기 좋은 경우:
- 반복되는 빌드/테스트/배포 단계를 패키징할 때
- Job 안에서 다른 단계와 상호작용해야 할 때
- workflow의 유연성은 유지하고 일부 단계만 재사용하고 싶을 때
- Secrets 전달을 직접 제어해야 하고 보안 요구가 높을 때
reusable workflow를 쓰기 좋은 경우:
- 전체 CI/CD 파이프라인을 표준화할 때
- 여러 프로젝트에 같은 전체 흐름이 필요할 때
- Secrets 자동 상속으로 설정을 줄이고 싶을 때
if,timeout-minutes같은 workflow 레벨 설정이 필요할 때
함께 사용하기:
실제로는 함께 사용하는 것이 모범 사례입니다. reusable workflow가 Composite Action을 호출하는 구조입니다.
# .github/workflows/ci.yml(reusable workflow)
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Composite Action 호출
- uses: ./.github/actions/build-test
with:
node-version: ${{ inputs.node-version }}
이렇게 하면 workflow 레벨의 표준 설정과 Action 레벨의 단계 재사용을 모두 얻을 수 있습니다.
5장: 버전 관리와 배포
5.1 Git Tags 모범 사례
Action 배포의 핵심은 Git Tags 관리입니다. semantic version + major tag의 이중 전략을 권장합니다.
# semantic version tag 생성
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0
# major tag 생성(최신 v1.x 추적)
git tag -fa v1 -m "Update v1 tag to latest"
git push origin v1 --force
v1.1.0을 배포할 때는 major tag를 갱신합니다.
git tag -a v1.1.0 -m "Add new feature"
git push origin v1.1.0
# v1 tag가 최신 버전을 가리키도록 갱신
git tag -fa v1 -m "Update v1 tag to v1.1.0"
git push origin v1 --force
이렇게 하면 사용자는 선택할 수 있습니다.
@v1.1.0으로 특정 버전을 고정@v1으로 v1.x 업데이트를 자동 수신
5.2 Marketplace 배포 흐름
GitHub Marketplace에 배포하려면 다음이 필요합니다.
1. README.md 준비
README에는 반드시 다음이 포함되어야 합니다.
- Action 이름과 설명
- Inputs와 Outputs 설명
- 사용 예제
- 라이선스
2. action.yml 준비
name, description, author, branding(선택) 필드가 완전한지 확인합니다.
3. Release 생성
GitHub 저장소 페이지에서:
- “Releases” → “Draft a new release”를 클릭합니다.
- tag를 선택합니다(예:
v1.0.0). - Release Notes를 작성합니다.
- “Publish this Action to the GitHub Marketplace”를 선택합니다.
- 배포합니다.
GitHub가 action.yml을 자동으로 검증한 뒤 Marketplace에 게시합니다.
5.3 자동화 배포 workflow
workflow를 만들어 자동으로 배포할 수도 있습니다.
name: Release Action
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update major tag
run: |
# major 버전 번호 추출
MAJOR=$(echo $GITHUB_REF | sed 's/refs\/tags\/v\([0-9]*\).*/\1/')
# major tag 갱신
git config user.name github-actions
git config user.email [email protected]
git tag -fa v$MAJOR -m "Update v$MAJOR tag"
git push origin v$MAJOR --force
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
이 workflow는 tag가 push될 때 major tag를 자동으로 갱신하고 Release를 생성합니다.
6장: 고급 팁과 모범 사례
6.1 Secrets 안전하게 전달하기
Composite Action은 secrets 컨텍스트에 직접 접근할 수 없습니다. 반드시 명시적으로 전달해야 합니다.
# workflow 안에서
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: ./.github/actions/deploy
with:
token: ${{ secrets.DEPLOY_TOKEN }}
env:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
# action.yml 안에서
inputs:
token:
description: 'Deploy token'
required: true
runs:
using: "composite"
steps:
- run: deploy --token ${{ inputs.token }}
shell: bash
env:
AWS_ACCESS_KEY: ${{ env.AWS_ACCESS_KEY }}
보안 권장사항:
- 민감한 정보는 inputs로 전달하지 않는 편이 좋습니다. 로그에서 보일 수 있습니다.
- 민감한 정보는
env로 전달하세요. masked 처리됩니다. - README에 필요한 Secrets를 명확히 적어 둡니다.
6.2 로컬 스크립트 사용하기
복잡한 로직은 YAML 안에 쓰기에 적합하지 않습니다. Action 디렉터리에 스크립트 파일을 둘 수 있습니다.
.github/actions/build-test/
├── action.yml
└── scripts/
└── build.sh
action.yml에서 호출합니다.
runs:
using: "composite"
steps:
- run: $GITHUB_ACTION_PATH/scripts/build.sh
shell: bash
$GITHUB_ACTION_PATH는 Composite Action의 루트 디렉터리 경로입니다.
6.3 조직 단위 Action 저장소 설계
팀에서는 공유 Action을 중앙에서 관리하는 방식을 권장합니다.
your-org/shared-actions/
├── .github/
│ └── workflows/
│ └── test.yml # 모든 Action 테스트
├── build/
│ ├── action.yml
│ └── README.md
├── deploy/
│ ├── action.yml
│ └── README.md
├── lint/
│ ├── action.yml
│ └── README.md
└── README.md
각 하위 디렉터리는 독립적인 Action이며, 완전한 README와 테스트를 갖습니다.
6.4 흔한 함정과 디버깅 팁
함정 1: 중첩 깊이 제한
Composite Action이 Composite Action을 호출할 때 최대 깊이는 10단계입니다. 초과하면 오류가 납니다. 디버깅이 어려워지므로 3단계를 넘기지 않는 편이 좋습니다.
함정 2: 환경 변수 scope
Composite Action 안의 env는 해당 단계 안에서만 유효합니다. 단계 간에 공유해야 한다면 GITHUB_ENV를 사용하세요.
steps:
- run: echo "MY_VAR=value" >> $GITHUB_ENV
shell: bash
- run: echo $MY_VAR # 접근 가능
shell: bash
디버깅 팁:
ACTIONS_STEP_DEBUG=true로 상세 로그를 켭니다.- 단계 안에
echo를 추가해 변수를 출력합니다. - SSH 디버깅에는
tmateAction을 사용할 수 있습니다. 프로덕션 환경에서는 권장하지 않습니다.
결론
Composite Action은 GitHub Actions 컴포넌트화의 핵심 도구입니다. 반복 설정에서 벗어나 CI 단계를 함수처럼 감쌀 수 있습니다.
세 가지를 기억하세요.
구조는 명확하게: action.yml은 inputs/outputs/steps를 정의하며 함수 시그니처를 설계하는 것과 비슷합니다. 파라미터화는 Action을 더 유연하게 만들고, 출력은 호출자가 결과를 가져갈 수 있게 합니다.
선택은 정확하게: Composite Action은 단일 기능(빌드, 테스트, 배포)을 감싸고, reusable workflow는 전체 파이프라인을 표준화합니다. 함께 사용할 때 가장 효과적입니다.
버전은 안전하게: commit SHA가 가장 안전하며 프로덕션 환경에 적합합니다. major tag는 유연하고 내부 프로젝트에 좋습니다. latest는 피하세요.
다음 단계는 프로젝트에 첫 Composite Action을 만들고 반복되는 build-test 단계를 패키징하는 것입니다. 그다음 Marketplace에 배포해 팀이나 커뮤니티와 공유해 보세요.
GitHub Actions Composite Action 개발하기
처음부터 재사용 가능한 Composite Action을 만들고 빌드/테스트 단계를 패키징합니다
⏱️ Estimated time: 30 min
- 1
Step 1: Action 디렉터리 구조 만들기
저장소에 Composite Action 디렉터리를 만듭니다:
• mkdir -p .github/actions/build-test
• action.yml 파일 생성 - 2
Step 2: action.yml 설정 작성하기
inputs, outputs, 실행 단계를 정의합니다:
• inputs로 파라미터 정의(node-version, test-command)
• outputs로 출력 정의(build-path, coverage)
• runs.steps에는 shell: bash를 반드시 명시 - 3
Step 3: 로컬 참조로 테스트하기
workflow에서 참조해 테스트합니다:
• uses: ./.github/actions/build-test
• with: node-version: '20'
• 로컬 테스트가 통과한 뒤 배포 - 4
Step 4: Git Tags 만들기
semantic version과 major tag를 함께 사용합니다:
• git tag -a v1.0.0 -m 'Initial release'
• git tag -fa v1 -m 'Update v1 tag'
• git push origin v1.0.0 v1 - 5
Step 5: Marketplace에 배포하기
GitHub UI에서 배포합니다:
• Releases → Draft a new release
• tag 선택(예: v1.0.0)
• Publish to Marketplace 선택
• GitHub가 자동 검증 후 게시
FAQ
Composite Action과 reusable workflow는 무엇이 다른가요?
Composite Action의 inputs에는 왜 type 필드가 없나요?
Composite Action에서 Secrets는 어떻게 전달하나요?
Action을 참조할 때 commit SHA와 tag 중 무엇을 써야 하나요?
Composite Action의 최대 중첩 깊이는 얼마인가요?
Composite Action에서는 shell을 반드시 명시해야 하나요?
4분 읽기 · 게시일: 2026년 5월 6일 · 수정일: 2026년 10월 21일
GitHub Actions 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
GitHub Actions 셀프 호스티드 Runner: 프라이빗 환경 배포 완벽 가이드
GitHub Actions 셀프 호스티드 Runner를 프라이빗 환경에 배포하는 완벽 가이드입니다. 2026년 요금 변경 분석, 세 가지 배포 방식 비교, 보안 모범 사례, 오픈 소스 관리 도구 Runner Fleet를 다룹니다.
10편 중 8편
다음
GitHub Actions 보안 실전: tj-actions 사건에서 배운 3가지 핵심 방어
GitHub Actions 보안 실전 가이드: tj-actions 공급망 공격을 깊이 분석하고 Secrets 관리, GITHUB_TOKEN 권한 제어, 감사 로그 설정을 익히며 2026 보안 로드맵의 새 기능까지 이해해 CI/CD 흐름을 키 유출과 권한 남용으로부터 보호합니다.
10편 중 10편



댓글
GitHub로 로그인하여 댓글을 남기세요