개발 도구
GitHub Actions로 Node.js CI/CD 파이프라인을 구성하는 방법 — 빌드부터 자동 배포까지
Jenkins 없이 GitHub 저장소 안에서 CI/CD를 완결하고 싶다면 GitHub Actions가 가장 빠른 선택이다. workflow 파일 작성부터 Secrets 관리, SSH 배포까지 순서대로 정리했다.

- ·GitHub Actions: 2019년 GA, .github/workflows/*.yml 파일로 파이프라인 정의
- ·runner: GitHub 호스팅(ubuntu-latest 등)과 self-hosted 두 가지 방식
- ·Secrets: 저장소 Settings에서 등록, ${{ secrets.이름 }} 으로 참조
- ·on: push, pull_request, schedule 등 다양한 이벤트로 워크플로 트리거 가능
Jenkins 서버를 따로 운영하기 부담스러운 사이드 프로젝트에서 GitHub Actions로 전환했다. workflow 파일을 저장소에 커밋하면 바로 CI가 돌아가는 구조가 Jenkins보다 훨씬 가볍게 느껴졌다. 처음에 SSH 배포 step에서 known_hosts 오류가 났는데, ssh-keyscan으로 호스트 키를 미리 등록하는 단계를 추가하니 해결됐다. Secrets에 등록한 값이 로그에 마스킹되는 걸 보고 보안 처리가 기본으로 되어 있다는 게 신뢰가 갔다.
GitHub Actions란 무엇인가
GitHub Actions가 Node.js CI/CD에 쓰이는 이유
GitHub Actions는 GitHub 저장소에 내장된 CI/CD 자동화 플랫폼이다. .github/workflows/ 경로에 YAML 파일을 만들어두면 push, pull request, 스케줄 같은 이벤트가 발생할 때 자동으로 파이프라인이 실행된다. Jenkins처럼 별도 서버를 프로비저닝하고 관리할 필요가 없다는 점이 가장 큰 장점이다. GitHub 저장소가 있다면 추가 인프라 없이 바로 CI/CD를 시작할 수 있다. GitHub 호스팅 runner를 사용하면 빌드 환경도 GitHub이 제공하기 때문에 서버 유지 비용이 없다. 공개 저장소는 무료이고 비공개 저장소도 월 2000분의 무료 사용량이 있어서 소규모 프로젝트에서는 비용 부담이 거의 없다. Node.js 프로젝트에서 자주 쓰이는 setup-node 액션으로 빌드에 사용할 Node.js 버전을 고정할 수 있고, npm ci와 npm run build를 순서대로 실행하는 단순한 구조로 대부분의 CI 요구를 충족한다. 직접 써보면 workflow 파일을 push한 순간 Actions 탭에서 실시간으로 실행되는 걸 볼 수 있어서, Jenkins 설정에 비해 피드백 루프가 훨씬 빠르다는 걸 체감하게 된다.
GitHub Actions와 Jenkins CI/CD 비교 — 어떤 상황에서 무엇을 쓰나
GitHub Actions와 Jenkins는 모두 CI/CD를 자동화하는 도구지만 운영 방식이 근본적으로 다르다. GitHub Actions는 GitHub 인프라 위에서 돌아가는 SaaS 방식이다. 별도 서버 없이 workflow 파일 하나로 시작할 수 있고 유지 관리 부담이 거의 없다. 대신 빌드 환경이 GitHub runner 사양에 묶이고, 외부 인터넷 접근이 필요한 배포는 네트워크 제약이 생길 수 있다. Jenkins는 직접 서버를 운영하는 방식이라 플러그인 2000개 이상의 생태계를 활용할 수 있고 내부망 환경에서도 동작한다. 대신 서버 설치, 업데이트, 보안 패치를 직접 관리해야 한다. 사이드 프로젝트나 GitHub을 중심으로 운영하는 팀이라면 GitHub Actions가 훨씬 가볍고 빠르다. 내부망 서버에 배포해야 하거나 복잡한 멀티 스테이지 파이프라인이 필요한 경우, 기존 Jenkins 인프라가 있는 조직이라면 Jenkins가 더 적합하다. 두 가지를 모두 써본 결과, 사이드 프로젝트나 작은 팀에서는 GitHub Actions의 진입 장벽이 낮고 관리 비용이 거의 없다는 점에서 우선 선택지로 쓰게 됐다.
GitHub Actions workflow 파일 작성하기
GitHub Actions로 Node.js 빌드를 자동화하는 workflow 파일 작성 방법
GitHub Actions workflow 파일은 .github/workflows/ 디렉토리에 .yml 확장자로 만든다. 파일 이름은 자유롭게 정할 수 있다. 파일 맨 위에 name으로 워크플로 이름을 지정하고, on 블록에서 어떤 이벤트에 실행될지 설정한다. push의 branches를 지정하면 해당 브랜치에 push될 때만 실행된다. jobs 블록에서 실제 실행할 작업을 정의한다. 각 job은 runs-on으로 실행 환경을 지정한다. ubuntu-latest를 쓰면 GitHub이 Ubuntu 최신 버전 runner를 제공한다. steps 블록에서 순서대로 실행할 단계를 정의한다. actions/checkout으로 코드를 내려받고, actions/setup-node로 Node.js 버전을 설정한다. setup-node의 node-version에 사용할 버전을 명시하면 Jenkins의 tools 블록과 같은 역할을 한다. 이후 npm ci로 의존성을 설치하고 npm run build로 빌드를 실행한다. 각 step에 name을 붙여두면 Actions 탭에서 어느 단계가 실패했는지 한눈에 파악할 수 있다.
# .github/workflows/ci.yml
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
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 buildGitHub Actions Secrets로 민감한 값을 안전하게 관리하는 방법
배포 자동화에는 SSH 개인키, API 토큰, 서버 주소 같은 민감한 정보가 필요하다. 이 값들을 workflow 파일에 직접 쓰면 저장소에 노출된다. GitHub Actions의 Secrets 기능을 사용하면 안전하게 관리할 수 있다. 저장소의 Settings → Secrets and variables → Actions에서 New repository secret을 클릭해 이름과 값을 등록한다. 등록된 Secrets는 workflow 파일에서 ${{ secrets.이름 }} 형태로 참조한다. Secrets 값은 Actions 실행 로그에 자동으로 마스킹되어 *** 로 표시된다. 실수로 로그에 출력하려 해도 GitHub이 자동으로 감춰준다. SSH 키는 개인키 전체 내용을 Secrets에 등록하고 배포 step에서 임시 파일로 저장해 사용하는 패턴이 일반적이다. ssh-agent 액션을 쓰면 개인키를 더 안전하게 관리할 수 있다. 서버 IP, 사용자 이름, 포트 같은 비민감 설정은 Variables(환경변수)로 따로 관리할 수 있다. Secrets와 Variables를 구분해서 사용하면 무엇이 진짜 시크릿인지 명확해진다. Secrets는 한 번 저장하면 값을 다시 볼 수 없고 업데이트만 가능하다.
GitHub Actions로 Node.js 앱을 서버에 배포하기
GitHub Actions workflow에서 SSH로 Node.js 앱을 서버에 배포하는 방법
빌드가 완료된 파일을 실제 서버에 배포하려면 배포 job 또는 deploy step을 추가한다. SSH를 통해 서버에 접속해 배포 스크립트를 실행하거나 rsync로 파일을 전송하는 방식이 일반적이다. appleboy/ssh-action 액션을 쓰면 SSH 연결과 명령 실행을 한 step으로 처리할 수 있다. host, username, key(SSH 개인키)를 Secrets에서 가져와 전달하고, script에 서버에서 실행할 명령을 작성하면 된다. 서버에서 git pull을 하고 npm ci, npm run build, pm2 restart를 순서대로 실행하는 방식이 흔히 쓰인다. rsync로 빌드 산출물만 전송하는 방식을 쓴다면 appleboy/scp-action 액션이 적합하다. CI job에서 빌드한 결과물을 artifacts로 저장하고 deploy job에서 내려받아 서버에 전송하는 패턴도 있다. 처음 SSH 배포를 설정할 때 StrictHostKeyChecking 때문에 연결이 실패하는 경우가 있다. ssh-keyscan으로 서버 호스트 키를 known_hosts에 미리 등록하거나, ssh-action의 설정으로 호스트 키 검증을 처리해야 한다.
# .github/workflows/deploy.yml (deploy job 부분)
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-app
git pull origin main
npm ci --production
npm run build
pm2 restart my-appGitHub Actions Node.js workflow에서 캐시를 활용해 빌드 속도를 높이는 방법
GitHub Actions의 가장 큰 단점 중 하나는 매번 빌드할 때마다 새 runner에서 시작해 node_modules를 처음부터 설치한다는 점이다. 프로젝트 의존성이 많으면 npm ci만으로 수 분이 걸릴 수 있다. actions/setup-node의 cache 옵션을 활용하면 node_modules 캐시를 재사용할 수 있다. cache: 'npm'으로 설정하면 package-lock.json을 캐시 키로 사용해서 lock 파일이 바뀌지 않은 경우 캐시된 node_modules를 그대로 쓴다. 의존성이 자주 바뀌지 않는 프로젝트에서는 설치 시간이 수십 초 수준으로 줄어드는 효과가 있다. Next.js 프로젝트라면 .next/cache 폴더도 캐싱하면 빌드 속도가 크게 개선된다. actions/cache 액션으로 .next/cache 경로를 캐시에 추가하면 된다. 캐시 키에 hashFiles('**/package-lock.json')를 포함시키면 의존성이 바뀔 때 자동으로 캐시가 무효화된다. 직접 캐시를 적용해보면 첫 번째 실행에서는 캐시 미스로 평소와 같은 시간이 걸리지만 두 번째부터는 눈에 띄게 빠르게 완료되는 걸 확인할 수 있다.
자주 묻는 질문
GitHub Actions와 Jenkins 중 어떤 것을 써야 하나요?+
GitHub을 쓰고 있고 별도 인프라 관리가 부담스러운 소규모 프로젝트라면 GitHub Actions를 추천합니다. 내부망 배포나 복잡한 파이프라인, 기존 Jenkins 인프라가 있는 환경이라면 Jenkins가 적합합니다.
GitHub Actions에서 SSH 배포 시 Host key verification failed 오류가 나면 어떻게 하나요?+
appleboy/ssh-action의 경우 known_hosts 파라미터에 ssh-keyscan 결과를 넣어야 합니다. 또는 배포 전 step에서 mkdir -p ~/.ssh && ssh-keyscan 서버IP >> ~/.ssh/known_hosts를 실행해 known_hosts를 미리 구성하면 됩니다.
Pull Request가 열릴 때도 빌드를 실행하려면 어떻게 하나요?+
on 블록에 pull_request: branches: [main]을 추가하면 됩니다. PR이 열리거나 업데이트될 때마다 빌드가 실행되고, PR 페이지에서 직접 결과를 확인할 수 있습니다.
특정 파일이 변경됐을 때만 workflow를 실행하려면 어떻게 하나요?+
on.push.paths 옵션을 쓰면 됩니다. paths: ['src/**', 'package.json']처럼 설정하면 해당 경로의 파일이 변경됐을 때만 workflow가 실행됩니다. 문서만 변경됐을 때 빌드를 건너뛰고 싶을 때 유용합니다.
관련 글
Jenkins GitHub Webhook 트리거 완벽 가이드 — push 후 즉시 빌드를 시작하는 방법
pollSCM 대신 GitHub Webhook을 연결하면 push 직후 Jenkins 빌드가 시작된다. Jenkins Webhook 수신 설정부터 GitHub 등록, Jenkinsfile 트리거 선언까지 순서대로 정리했다.
Next.js 정적 사이트를 Jenkins로 자동 배포하는 방법 — Jenkinsfile로 빌드부터 배포까지
매번 수동으로 빌드하고 서버에 올리던 Next.js 정적 사이트 배포를 Jenkins 파이프라인으로 자동화한 방법을 정리했다. Checkout부터 scp 배포까지 단계별로 설명한다.
Jenkins 빌드 결과를 Slack으로 알림 보내는 방법 — Slack Notification 플러그인 완벽 가이드
Jenkins 빌드가 끝날 때마다 성공/실패 알림을 Slack으로 받으면 배포 상태를 실시간으로 파악할 수 있다. Slack Notification 플러그인 설치부터 Webhook 설정, Jenkinsfile post 블록 작성까지 정리했다.