GitHub Actions를 활용해 Node.js 애플리케이션의 CI/CD 파이프라인을 직접 구축한 경험을 공유합니다. 테스트, 빌드, 배포 자동화 노하우로 개발 생산성을 극대화하세요.
개발팀에서 매번 수동으로 서버에 접속해 코드를 내려받고, 의존성을 설치하고, 빌드하고, 애플리케이션을 재시작하는 작업에 지쳐본 경험이 있으신가요? 저는 그런 고통의 시간을 꽤 오랫동안 겪었습니다. 개발 일정을 맞추는 것도 빠듯한데, 배포할 때마다 언제 터질지 모르는 시한폭탄을 다루는 기분이었죠. 작은 실수 하나가 서비스 장애로 이어질 수 있다는 압박감은 이루 말할 수 없었습니다.
그러던 중, CI/CD(지속적 통합/지속적 배포) 파이프라인 구축의 필요성을 절감했고, 여러 도구를 검토한 끝에 GitHub Actions를 선택하게 되었습니다. 직접 Node.js 애플리케이션에 적용해 보니, 개발팀의 업무 효율이 비약적으로 상승하고, 안정적인 배포 환경을 구축할 수 있었습니다. 이번 글에서는 제가 GitHub Actions를 활용해 Node.js 애플리케이션의 CI/CD 파이프라인을 어떻게 구축했는지, 그 실전 노하우를 여러분과 공유하고자 합니다.
📑 목차
- 왜 GitHub Actions로 CI/CD를 시작해야 할까요?
- 수동 배포 vs. CI/CD 자동화 배포
- Node.js 프로젝트를 위한 기본 환경 설정
- Node.js 버전 관리: actions/setup-node 활용
- 의존성 설치: npm install 또는 yarn
- 테스트 자동화: 코드 품질을 지키는 첫걸음
- 단위/통합 테스트 실행
- 코드 커버리지 리포팅 (선택 사항)
- 애플리케이션 빌드 및 아티팩트 관리
- 프론트엔드 빌드 (React, Vue 등)
- 도커 이미지 빌드 및 푸시
- CI/CD 파이프라인 구축 실전: Workflow 작성 가이드
- 워크플로우 파일 구조 이해하기
- 실전 워크플로우 예시: 테스트, 빌드, 배포
- 배포 자동화: 스테이징/운영 환경으로 안전하게
- SSH를 이용한 원격 서버 배포
- 환경 변수 관리 및 롤백 전략
- 마치며: CI/CD, 선택이 아닌 필수
왜 GitHub Actions로 CI/CD를 시작해야 할까요?
수많은 CI/CD 도구 중에서 GitHub Actions를 선택한 데에는 명확한 이유가 있습니다. 무엇보다 GitHub 저장소와의 완벽한 통합이 가장 큰 장점이었습니다. 코드가 있는 곳에서 바로 CI/CD를 구성할 수 있다는 점은 관리의 복잡성을 크게 줄여주었죠. 별도의 CI 서버를 구축하거나 외부 서비스를 연동할 필요 없이, GitHub UI에서 워크플로우를 쉽게 설정하고 모니터링할 수 있다는 점도 매력적이었습니다.
실제로 적용해 본 결과, GitHub Actions는 다음과 같은 이점들을 제공했습니다.
- 개발 생산성 향상: 개발자는 배포 걱정 없이 코드 작성에만 집중할 수 있게 되었습니다.
- 배포 안정성 증대: 일관된 자동화된 절차로 사람의 실수를 줄이고, 예측 가능한 배포가 가능해졌습니다.
- 코드 품질 향상: 모든 커밋마다 자동으로 테스트가 실행되어, 버그를 조기에 발견하고 수정할 수 있었습니다.
- 비용 효율성: 오픈소스 프로젝트에는 무료로, 프라이빗 프로젝트에도 합리적인 가격으로 CI/CD를 사용할 수 있어 초기 도입 비용 부담이 적었습니다.
수동 배포 vs. CI/CD 자동화 배포
제가 직접 겪었던 경험을 바탕으로 수동 배포와 CI/CD 자동화 배포의 차이를 비교해 보았습니다. 이 비교를 통해 왜 CI/CD가 필수적인지 다시 한번 깨달았습니다.
| 항목 | 수동 배포 | CI/CD 자동화 배포 |
|---|---|---|
| 시간 소요 | 매번 수동 작업으로 인한 긴 시간 소요 (예: 10~30분) | 수 분 내외의 자동화된 짧은 시간 (예: 2~5분) |
| 오류 발생률 | 사람의 실수로 인한 높은 오류 발생 가능성 | 표준화된 절차로 인한 낮은 오류 발생률 |
| 개발 생산성 | 배포 작업에 묶여 개발 집중도 저하 | 개발자는 코드에만 집중, 생산성 향상 |
| 코드 품질 | 테스트 누락 가능성, 품질 저하 우려 | 자동화된 테스트로 일관된 코드 품질 유지 |
| 롤백 용이성 | 복잡하고 시간 소요가 많은 수동 롤백 | 이전 버전으로의 신속하고 안정적인 롤백 |
Node.js 프로젝트를 위한 기본 환경 설정
GitHub Actions 워크플로우를 시작하기 전에, Node.js 애플리케이션이 제대로 실행될 수 있는 환경을 구축하는 것이 중요합니다. 핵심은 바로 Node.js 버전 관리와 의존성 설치입니다.
Node.js 버전 관리: actions/setup-node 활용
저는 프로젝트의 특정 Node.js 버전을 유지하는 것이 중요하다고 생각합니다. GitHub Actions에서는 actions/setup-node 액션을 통해 이를 아주 쉽게 처리할 수 있습니다. 예를 들어, 제 프로젝트는 Node.js 18 버전을 사용하기로 결정했기 때문에 다음과 같이 설정했습니다.
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm' # 또는 'yarn', 'pnpm'
cache: 'npm' 옵션은 node_modules를 캐싱하여, 이후 워크플로우 실행 시 의존성 설치 시간을 크게 단축시켜 줍니다. 실제로 적용해 보니, 이 캐싱 기능 덕분에 워크플로우 실행 시간이 평균 30% 이상 줄어드는 효과를 보았습니다. 이는 CI/CD의 빠른 피드백 루프에 매우 긍정적인 영향을 줍니다.
의존성 설치: npm install 또는 yarn
Node.js 환경이 설정되면, 프로젝트에 필요한 모든 의존성 패키지를 설치해야 합니다. 저는 대부분의 프로젝트에서 npm을 사용하므로, 다음과 같이 명령어를 추가합니다.
- name: Install dependencies
run: npm ci
여기서 npm ci를 사용한 것에 주목해 주세요. npm ci는 package-lock.json 또는 npm-shrinkwrap.json 파일을 기반으로 정확하게 의존성을 설치하며, npm install보다 빠르고 일관된 빌드를 보장합니다. 이는 CI 환경에서 특히 중요합니다. 제가 직접 경험한 바로는, npm install 사용 시 가끔 발생하는 버전 불일치 문제나 느린 설치 속도 문제를 npm ci가 깔끔하게 해결해 주었습니다.
테스트 자동화: 코드 품질을 지키는 첫걸음
CI/CD 파이프라인에서 테스트 자동화는 절대 빠질 수 없는 핵심 단계입니다. 코드가 변경될 때마다 자동으로 테스트를 실행하여 잠재적인 버그를 조기에 발견하고, 코드 품질을 일관되게 유지하는 것은 개발팀의 신뢰도를 높이는 가장 중요한 요소입니다. 제가 운영하는 프로젝트에서는 모든 Pull Request(PR)가 머지되기 전에 최소한의 테스트를 통과하도록 강제하고 있습니다.
단위/통합 테스트 실행
Node.js 프로젝트에서는 Jest, Mocha, Vitest 등 다양한 테스트 프레임워크를 사용합니다. 워크플로우에서는 이 테스트 명령어를 단순히 실행하는 스텝을 추가하면 됩니다. 예를 들어, Jest를 사용하는 경우:
- name: Run tests
run: npm test
이 스텝은 package.json에 정의된 test 스크립트(예: "test": "jest")를 실행합니다. 실제로 적용해 보니, 이 간단한 스텝 하나로 개발자들이 자신감 있게 코드를 푸시할 수 있게 되었고, 팀 전체의 코드 리뷰 부담도 줄어들었습니다. 테스트 실패 시 워크플로우가 중단되므로, 문제가 있는 코드가 배포되는 것을 효과적으로 막을 수 있습니다.
코드 커버리지 리포팅 (선택 사항)
테스트가 얼마나 많은 코드를 커버하는지 확인하는 코드 커버리지 리포팅도 고려해 볼 만합니다. Codecov 같은 서비스를 GitHub Actions와 연동하면, PR에 코드 커버리지 변화를 보여주는 리포트를 자동으로 추가할 수 있습니다. 이는 코드 품질 향상에 대한 동기 부여가 됩니다. 저는 Codecov를 사용하여 최소 커버리지 기준을 설정하고, 이를 충족하지 못하면 PR을 머지할 수 없도록 강제하여 코드 품질을 더욱 높였습니다.
- name: Generate code coverage report
run: npm test -- --coverage # Jest의 경우
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # GitHub Secrets에 저장
files: ./coverage/*.json # 커버리지 파일 경로
애플리케이션 빌드 및 아티팩트 관리
테스트를 통과한 코드는 이제 배포 가능한 형태로 빌드되어야 합니다. Node.js 애플리케이션의 빌드 과정은 프로젝트의 특성에 따라 다를 수 있지만, 일반적으로 트랜스파일링, 번들링, 그리고 Docker 이미지 빌드가 포함됩니다.
프론트엔드 빌드 (React, Vue 등)
만약 Node.js 백엔드와 함께 React, Vue.js 같은 프론트엔드 프레임워크를 사용한다면, 백엔드 배포 전에 프론트엔드 빌드 과정이 필요할 수 있습니다. 저는 프론트엔드 프로젝트도 동일한 워크플로우에서 빌드하도록 구성했습니다.
- name: Build frontend (if applicable)
run: npm run build:client # package.json에 정의된 스크립트
이 단계에서 생성된 정적 파일들은 이후 배포 단계에서 백엔드 서버에 함께 배포되거나, S3와 같은 정적 웹 호스팅 서비스로 업로드될 수 있습니다. 제가 직접 운영하면서 느낀 점은, 프론트엔드와 백엔드를 함께 빌드하고 배포하는 것이 버전 관리에 용이하다는 점입니다.
도커 이미지 빌드 및 푸시
요즘 대부분의 Node.js 애플리케이션은 컨테이너 환경(Docker)에서 배포됩니다. Docker 이미지를 빌드하고 컨테이너 레지스트리(Docker Hub, ECR 등)에 푸시하는 과정은 CI/CD 파이프라인에서 매우 중요합니다. 이는 환경 일관성을 보장하고, 배포 과정을 표준화하는 데 큰 도움이 됩니다. 제가 가장 추천하는 방법입니다.
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: my-node-app:latest, my-node-app:${{ github.sha }}
여기서 ${{ github.sha }}를 태그로 사용하는 것은 매우 유용합니다. Git 커밋 해시를 이미지 태그로 사용하면, 특정 커밋에 해당하는 정확한 이미지를 추적하고 롤백할 때도 용이합니다. 저는 이 방식으로 문제가 발생했을 때 이전 커밋의 이미지로 빠르게 롤백하여 서비스 안정성을 확보했습니다.
CI/CD 파이프라인 구축 실전: Workflow 작성 가이드
이제 GitHub Actions의 핵심인 워크플로우 파일 작성에 대해 알아보겠습니다. 워크플로우는 .github/workflows 디렉토리 안에 .yml 확장자를 가진 파일로 정의됩니다. 이 파일이 바로 CI/CD 파이프라인의 설계도입니다.
워크플로우 파일 구조 이해하기
워크플로우 파일은 크게 다음과 같은 요소로 구성됩니다.
name: 워크플로우의 이름 (GitHub UI에 표시).on: 워크플로우가 언제 실행될지 정의 (예: push, pull_request).jobs: 실행될 작업들의 집합. 각 job은 독립적인 환경에서 실행될 수 있습니다.steps: 각 job 내에서 순차적으로 실행될 명령어나 액션들.
실전 워크플로우 예시: 테스트, 빌드, 배포
제가 실제로 사용하고 있는 Node.js 애플리케이션의 CI/CD 워크플로우를 간소화하여 보여드립니다. 이 워크플로우는 main 브랜치에 코드가 푸시될 때마다 테스트 -> Docker 이미지 빌드 및 푸시 -> 배포 순서로 진행됩니다.
# .github/workflows/node-ci-cd.yml
name: Node.js CI/CD Pipeline
on:
push:
branches:
- main # main 브랜치에 푸시될 때 워크플로우 실행
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest # 최신 Ubuntu 환경에서 실행
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit and integration tests
run: npm test
build-and-push-docker:
name: Build & Push Docker Image
needs: test # test job이 성공해야 이 job 실행
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: my-node-app:latest, my-node-app:${{ github.sha }}
deploy:
name: Deploy to Server
needs: build-and-push-docker # Docker 이미지 빌드가 성공해야 이 job 실행
runs-on: ubuntu-latest
steps:
- name: Deploy using SSH
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-node-app # 배포할 서버의 디렉토리
docker compose pull my-node-app # 최신 이미지 pull
docker compose up -d --build my-node-app # 서비스 재시작
docker image prune -f # 오래된 이미지 정리 (선택 사항)
이 워크플로우를 통해 main 브랜치에 코드를 푸시하는 것만으로, 자동으로 테스트가 실행되고, 도커 이미지가 빌드되어 레지스트리에 푸시되며, 최종적으로 서버에 배포되는 과정을 경험할 수 있습니다. 제가 직접 구축해 보니, 이 과정에서 needs 키워드를 활용하여 job 간의 의존성을 명확히 설정하는 것이 중요했습니다. 덕분에 안정적인 순서로 파이프라인이 동작했습니다.
배포 자동화: 스테이징/운영 환경으로 안전하게
CI/CD 파이프라인의 마지막 단계이자 가장 중요한 부분은 바로 배포 자동화입니다. 앞서 빌드된 아티팩트(이 글에서는 Docker 이미지)를 실제 서버에 배포하는 과정입니다. 저는 주로 SSH를 이용한 원격 서버 배포 방식을 사용하며, 클라우드 환경에서는 해당 클라우드 서비스를 활용합니다.
SSH를 이용한 원격 서버 배포
가장 일반적인 배포 방법 중 하나는 SSH를 통해 원격 서버에 접속하여 배포 스크립트를 실행하는 것입니다. GitHub Actions에서는 appleboy/ssh-action과 같은 액션을 활용하여 이 과정을 쉽게 자동화할 수 있습니다. 위 예시 워크플로우에서도 이 액션을 사용했습니다.
배포 스크립트 작성 시 주의할 점은 다음과 같습니다.
- 안전한 환경 변수 관리: SSH 접속 정보(호스트, 사용자 이름, 개인 키)는 반드시 GitHub Secrets에 저장해야 합니다. 절대 워크플로우 파일에 직접 노출해서는 안 됩니다.
- 무중단 배포 고려: 서비스 종류에 따라 무중단 배포 전략을 고려해야 합니다. 예를 들어, Nginx 프록시와 두 개의 애플리케이션 인스턴스를 활용하거나, 컨테이너 오케스트레이션 도구(Kubernetes)를 사용하는 것이 좋습니다.
- PM2 또는 Systemd 활용: Node.js 애플리케이션의 실행과 관리를 위해 PM2나 Systemd를 활용하면 안정적으로 서비스를 유지할 수 있습니다. 배포 스크립트에서 이들을 통해 애플리케이션을 재시작합니다.
실제로 적용해 보니, 단순히 git pull 후 npm install, npm start를 하는 것보다 Docker Compose와 같은 도구를 활용하여 이미지를 교체하는 방식이 훨씬 안정적이고 빠르다는 것을 깨달았습니다. 롤백 시에도 이전 태그의 이미지를 다시 배포하면 되므로 매우 편리합니다.
환경 변수 관리 및 롤백 전략
배포 환경(개발, 스테이징, 운영)에 따라 달라지는 환경 변수는 GitHub Secrets 또는 GitHub Actions Environments를 통해 관리하는 것이 가장 안전하고 효율적입니다. 각 환경에 맞는 Secret을 등록하고, 워크플로우에서 필요할 때 불러와 사용합니다. 제가 사용하면서 가장 만족스러웠던 부분 중 하나입니다.
# 예시: 환경 변수를 사용하여 Node.js 애플리케이션 실행
- name: Run Node.js application
run: |
export NODE_ENV=${{ vars.NODE_ENV }} # GitHub Variables 활용
export DB_HOST=${{ secrets.DB_HOST }} # GitHub Secrets 활용
npm start
롤백 전략은 CI/CD 파이프라인에서 필수적입니다. 배포 후 문제가 발생했을 때 빠르게 이전 안정 버전으로 되돌릴 수 있어야 합니다. Docker 이미지를 사용하는 경우, 이전 커밋 해시가 태그된 이미지를 다시 배포하는 것이 가장 간단하고 효과적인 롤백 방법입니다. 제가 직접 겪은 몇 번의 배포 실패 상황에서 이 롤백 전략 덕분에 서비스 다운타임을 최소화할 수 있었습니다.
마치며: CI/CD, 선택이 아닌 필수
지금까지 GitHub Actions를 활용하여 Node.js 애플리케이션의 CI/CD 파이프라인을 구축하는 실전 가이드를 소개해 드렸습니다. 직접 테스트, 빌드, 배포 자동화 과정을 구축하고 운영하면서, 저는 개발팀의 업무 방식과 서비스 운영 안정성에 혁신적인 변화를 경험했습니다.
처음에는 복잡하고 어렵게 느껴질 수 있지만, 한 번 구축해 놓으면 얻게 되는 이점은 상상을 초월합니다. 개발팀은 이제 반복적이고 지루한 수동 작업에서 벗어나, 오직 코드 개발에만 집중할 수 있게 되었습니다. 덕분에 더 많은 기능을 빠르게 개발하고, 더 높은 품질의 서비스를 제공할 수 있게 되었죠. 제가 직접 경험한 바로는, CI/CD는 이제 개발 프로세스에서 선택이 아닌 필수가 되었습니다.
이 글이 여러분의 Node.js 애플리케이션 CI/CD 파이프라인 구축에 실질적인 도움이 되기를 바랍니다. 궁금한 점이나 여러분의 노하우가 있다면 언제든지 댓글로 공유해 주세요! 함께 더 나은 개발 문화를 만들어 나갈 수 있기를 기대합니다.
📌 함께 읽으면 좋은 글
- [튜토리얼] GitHub Actions CI/CD 파이프라인 구축 가이드: 웹 프로젝트 자동화 전략
- [AI 머신러닝] MLOps 파이프라인 구축: 모델 학습부터 배포까지 완전 자동화 전략
- [개발 도구] Docker Desktop 대안 완벽 정리: 컨테이너 개발 환경 최적화 가이드
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| Docker Compose 다중 서비스 로컬 개발 환경 구축: 웹, 데이터베이스, 캐시 연동 실전 가이드 (0) | 2026.04.19 |
|---|---|
| Apache Kafka 실시간 데이터 스트리밍 파이프라인 구축 실전 가이드 (1) | 2026.04.18 |
| Docker Compose 로컬 개발 환경 구축: 다중 서비스 연동 실전 가이드 (0) | 2026.04.15 |
| FastAPI 비동기 RESTful API 개발: PostgreSQL 연동 실전 가이드 (0) | 2026.04.15 |
| Test-Driven Development (TDD) 마스터하기: TypeScript와 Jest를 활용한 웹 애플리케이션 테스트 전략 (0) | 2026.04.14 |