왜 이 글을 쓰나
프로젝트를 GitHub에 올릴 때마다 손으로 빌드하고, 직접 배포 명령을 치고, 결과를 확인하는 과정을 반복하고 있었다. 실수도 잦았다. 빌드 전 커밋을 올리거나, 배포 명령을 빠뜨리거나.
GitHub Actions를 붙이고 나서는 git push 하나로 끝난다. 빌드, 테스트, 배포가 자동으로 돌아간다. 설정 파일 하나가 그 모든 걸 대체한다.
이 글에서는 GitHub Actions의 개념부터 실제 .github/workflows/deploy.yml 예시까지 정리한다. GitHub Pages 연동도 포함이다.
GitHub Actions 개념
GitHub Actions는 GitHub 저장소에 내장된 CI/CD 플랫폼이다. 별도 서비스 연동 없이 저장소 안에서 파이프라인을 구성할 수 있다.
핵심 용어를 먼저 정리한다:
| 용어 | 설명 |
|---|---|
| Workflow | 자동화 단위. .github/workflows/*.yml 파일로 정의한다 |
| Event | Workflow를 실행하는 트리거 (push, PR, schedule 등) |
| Job | Workflow 안의 실행 단위. 병렬/순차 실행 가능 |
| Step | Job 안의 명령어 단위. shell 명령 또는 Action |
| Action | 재사용 가능한 Step 묶음. GitHub Marketplace에서 가져다 쓴다 |
| Runner | Job이 실행되는 가상 머신 (ubuntu-latest, windows-latest 등) |
구조를 도식으로 보면:
Workflow
└── Event (on: push)
└── Job (build-and-deploy)
├── Step 1: checkout
├── Step 2: install
├── Step 3: build
└── Step 4: deploy
실제 workflow yaml — GitHub Pages 자동 배포
React 프로젝트를 GitHub Pages에 자동으로 배포하는 완전한 예시다.
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches:
- main # main 브랜치에 push될 때만 실행
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./dist"
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
주요 포인트를 설명한다:
on: push: branches: [main] — main 브랜치 push 시에만 실행된다. feature 브랜치에서 작업하면 워크플로가 돌지 않는다.
permissions — GitHub Pages 배포에는 pages: write와 id-token: write 권한이 필요하다. 없으면 배포 단계에서 권한 오류가 난다.
npm ci — npm install 대신 npm ci를 쓴다. package-lock.json을 기준으로 정확히 설치하고, 속도도 빠르다. CI 환경에서는 항상 ci를 쓰는 것이 맞다.
cache: "npm" — node_modules를 캐시한다. 처음 실행 이후에는 설치 시간이 크게 줄어든다.
needs: build — deploy Job이 build Job 완료 후에만 실행된다. 순서 보장이다.
secrets.VITE_API_URL — 환경변수는 저장소 Settings → Secrets에 등록하고 참조한다. 하드코딩하면 보안 문제가 생긴다.
주요 트리거 패턴
트리거는 목적에 맞게 골라야 한다.
| 트리거 | yaml 예시 | 언제 쓰나 |
|---|---|---|
| push (특정 브랜치) | on: push: branches: [main] |
메인 브랜치 배포 자동화 |
| pull_request | on: pull_request: branches: [main] |
PR 열릴 때 테스트 실행 |
| schedule | on: schedule: - cron: '0 9 * * 1' |
정기 작업 (매주 월요일 오전 9시) |
| workflow_dispatch | on: workflow_dispatch |
수동 실행 버튼 추가 |
| push (특정 경로) | on: push: paths: ['src/**'] |
특정 디렉토리 변경 시만 실행 |
workflow_dispatch는 특히 유용하다. GitHub Actions 탭에서 직접 실행 버튼이 생겨서, 자동 트리거 없이도 수동으로 워크플로를 돌릴 수 있다.
PR 열릴 때 테스트만 도는 예시
배포와 별개로 PR 리뷰 시 테스트를 자동으로 실행하는 워크플로다.
# .github/workflows/test.yml
name: Run Tests on PR
on:
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test -- --coverage
이렇게 하면 PR을 열 때마다 린트와 테스트가 돌고, GitHub UI에서 통과/실패 여부가 바로 보인다. 머지 전 체크 수단으로 쓴다.
GitHub Actions vs 타 CI/CD 비교
| GitHub Actions | Jenkins | CircleCI | Vercel | |
|---|---|---|---|---|
| 설정 위치 | 저장소 내 yaml | 별도 서버 | 외부 서비스 | 플랫폼 내장 |
| 무료 한도 | 공개: 무제한 / 비공개: 2,000분/월 | 자체 서버 비용 | 6,000분/월 | 무료 플랜 있음 |
| 설치 필요 | 없음 | 서버 필요 | 없음 | 없음 |
| 커스텀 환경 | Runner 자유 설정 | 높음 | 중간 | 제한적 |
| Marketplace | GitHub Marketplace | 플러그인 생태계 | Orbs | 없음 |
| 학습 곡선 | 낮음 | 높음 | 중간 | 매우 낮음 |
GitHub을 쓰고 있다면 Actions가 가장 자연스러운 선택이다. 저장소와 같은 곳에 설정 파일이 있고, PR/이슈/릴리즈 이벤트와 직접 연동된다.
GitHub Pages 설정 주의사항
yaml만 작성한다고 배포되지 않는다. 저장소 설정도 맞춰야 한다.
1. Pages 소스를 "GitHub Actions"로 변경한다
저장소 → Settings → Pages → Source를 Deploy from a branch가 아닌 GitHub Actions로 바꾼다. 이걸 안 하면 deploy job이 권한 오류로 실패한다.
2. Vite 프로젝트는 base 설정이 필요하다
https://username.github.io/repo-name/ 경로로 배포될 경우, vite.config.js에 base를 설정해야 정적 파일 경로가 맞다:
// vite.config.js
export default {
base: '/repo-name/',
}
3. SPA 라우팅 이슈
React Router 같은 클라이언트 사이드 라우팅을 쓰면 새로고침 시 404가 난다. 404.html을 index.html로 복사하는 우회책이 있다:
- name: Copy index.html to 404.html for SPA routing
run: cp dist/index.html dist/404.html
삽질 기록
권한 오류로 deploy가 계속 실패했다
permissions 블록을 빠뜨렸던 게 원인이었다. Actions 탭에서 보면 "Resource not accessible by integration"이라는 메시지가 뜬다. pages: write와 id-token: write를 추가하면 해결된다.
npm install로 했더니 lock 파일이 바뀌는 문제
CI 환경에서 npm install을 쓰면 package-lock.json이 업데이트되면서 커밋되지 않은 변경이 생기는 경우가 있다. npm ci는 lock 파일을 수정하지 않는다. CI에서는 ci 명령을 써야 한다.
캐시가 쌓이면서 구버전 의존성이 남았다
cache: "npm"을 켜두면 node_modules를 재사용하는데, 가끔 낡은 캐시가 남아서 이상하게 동작했다. Actions 탭 → Caches에서 수동으로 삭제하거나, cache key에 날짜나 파일 해시를 포함해서 강제로 갱신할 수 있다.
환경변수를 yaml에 직접 써서 보안 경고가 떴다
API 키를 env: KEY: "actual-key"로 하드코딩했다가 GitHub의 secret scanning에 걸렸다. Secrets에 등록하고 ${{ secrets.KEY }}로 참조해야 한다.
마무리
GitHub Actions는 진입 장벽이 낮은 편이다. yaml 파일 하나, 저장소 설정 한 곳만 바꾸면 push 이후의 빌드/배포가 자동으로 돌아간다.
처음에는 deploy.yml 하나로 시작하는 걸 권한다. 배포가 안정되면 test.yml을 따로 분리하고, 나중에 환경별 워크플로(staging, production)를 추가하면 된다. 복잡한 구성보다 작동하는 단순한 것이 먼저다.
기술 스택: GitHub Actions, Node.js 20, Vite, GitHub Pages
참고: GitHub Actions 공식 문서 | GitHub Marketplace
'개발 팁' 카테고리의 다른 글
| 백업 없이 MySQL/MariaDB 데이터 복구하기 (0) | 2026.03.18 |
|---|---|
| 정규식 실전 치트시트 — 개발자가 자주 쓰는 패턴 모음 (0) | 2026.03.18 |
| .env & 환경변수 관리 실전 — 팀 프로젝트부터 실수 대처법까지 (0) | 2026.03.17 |
| 터미널 생산성 세팅 — zsh + CLI 도구 실사용 정리 (0) | 2026.03.17 |
| Chrome DevTools 실전 활용 — F12 열고 Console만 쓰고 있다면 (0) | 2026.03.17 |