git push 후 자동 검사가 시작되는 이유: Continuous Integration(CI) 입문

코드를 수정하고 로컬에서 잘 동작하는 것까지 확인했습니다. 이제 아래처럼 GitHub에 올립니다.

git add .
git commit -m "feat: 회원가입 기능 추가"
git push origin main

잠시 뒤 GitHub 저장소에 초록색 체크가 나타납니다. 반대로 타입 오류나 테스트 실패가 있으면 빨간색 표시와 함께 배포가 멈춥니다. 개발자가 서버에 접속해 검사 명령을 하나씩 실행하지 않았는데도 가능한 이유가 바로 Continuous Integration, 줄여서 CI를 구성했기 때문입니다.

CI를 한 문장으로 설명하면

CI는 여러 개발자의 코드 변경을 자주 합치고, 합쳐도 안전한지를 자동으로 확인하는 개발 방식입니다. 보통 저장소에 코드가 올라오면 깨끗한 실행 환경에서 의존성을 설치하고 타입 검사, 린트, 테스트, 빌드를 반복합니다.

중요한 점은 CI가 단순한 자동 배포의 다른 이름이 아니라는 것입니다.

CI(Continuous Integration): 코드가 합쳐져도 괜찮은지 지속적으로 검사합니다.

CD(Continuous Delivery/Deployment): 검사를 통과한 결과물을 전달하거나 운영 환경에 배포합니다.

CI가 품질 확인이라면 CD는 전달 과정입니다. 작은 프로젝트에서는 하나의 GitHub Actions 워크플로에 두 과정을 연달아 넣기도 하지만, 개념과 실패 지점은 구분해 두는 편이 좋습니다.

git push 뒤에서 실제로 일어나는 일

GitHub Actions를 예로 들면 흐름은 다음과 같습니다.

개발자 코드 수정
↓
git commit
↓
git push
↓
GitHub가 push 이벤트 감지
↓
새 실행 환경에서 저장소 체크아웃
↓
의존성 설치 → 타입 검사 → 린트 → 테스트 → 빌드
↓
성공: 초록색 체크 / 실패: 로그와 함께 빨간색 표시

검사는 개발자의 노트북이 아니라 GitHub가 준비한 별도 실행 환경에서 진행됩니다. 그래서 “내 컴퓨터에서는 됐는데요”라는 상황을 줄일 수 있습니다. 같은 Node.js 버전과 같은 잠금 파일을 사용하면 누가 푸시하더라도 재현 가능한 조건에서 확인할 수 있습니다.

가장 작은 GitHub Actions CI 예제

Node.js 프로젝트라면 저장소에 .github/workflows/ci.yml 파일을 만들고 다음과 같이 시작할 수 있습니다.

name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Test
run: npm test

- name: Build
run: npm run build

GitHub는 워크플로 파일을 .github/workflows에 두도록 규정하며, push 이벤트에 브랜치 필터를 설정할 수 있습니다. 위 예시는 main과 develop에 푸시할 때 실행되고, main을 대상으로 한 Pull Request에서도 실행됩니다. 자세한 이벤트 문법은 GitHub Actions 워크플로 문법에서 확인할 수 있습니다.

공식 Node.js 빌드·테스트 가이드도 저장소 체크아웃, Node.js 설정, npm ci, 빌드와 테스트를 기본 흐름으로 안내합니다. 액션 버전과 Node.js 버전은 시간이 지나면 바뀔 수 있으므로 프로젝트 런타임과 공식 문서를 기준으로 갱신해야 합니다.

각 단계는 무엇을 막아 주나

npm ci — 설치 결과를 고정한다

npm ci는 잠금 파일에 기록된 의존성 버전을 기준으로 새로 설치합니다. CI에서 npm install보다 선호되는 이유는 실행할 때마다 의존성 조합이 달라지는 일을 줄일 수 있기 때문입니다. 잠금 파일과 패키지 설정이 맞지 않으면 즉시 실패하므로 저장소 상태의 문제도 빠르게 드러납니다.

타입 검사 — 실행 전에 잘못된 연결을 찾는다

TypeScript 프로젝트에서는 존재하지 않는 속성을 읽거나 잘못된 타입을 전달한 코드를 실행 전에 발견할 수 있습니다. 화면을 직접 눌러 보지 않아도 많은 연결 오류를 잡아 줍니다.

린트 — 팀의 코드 규칙을 자동 적용한다

사용하지 않는 변수, 위험한 패턴, 프레임워크 규칙 위반 등을 검사합니다. 사람마다 다른 리뷰 기준 중 기계가 판정할 수 있는 부분을 CI에 넘기는 것입니다.

테스트 — 기존 동작이 깨지지 않았는지 확인한다

새 기능이 잘 작동하는 것만큼 기존 기능이 계속 작동하는지도 중요합니다. 로그인 코드를 고쳤는데 결제 테스트가 실패한다면 코드 사이의 예상하지 못한 영향을 푸시 직후 알 수 있습니다.

빌드 — 실제 배포 형태로 만들 수 있는지 확인한다

개발 서버가 켜지는 것과 프로덕션 빌드가 성공하는 것은 다릅니다. 번들 생성, 서버·클라이언트 코드 경계, 환경 변수 참조 같은 문제는 빌드 단계에서 드러날 수 있습니다.

한 단계가 실패하면 어떻게 될까

GitHub Actions의 각 run 단계는 기본적으로 앞 단계가 성공해야 다음 단계로 넘어갑니다. 예를 들어 타입 검사에서 오류가 발생하면 워크플로는 실패로 끝나고, 이후 배포 작업을 실행하지 않도록 구성할 수 있습니다.

실패 자체는 나쁜 결과가 아닙니다. 문제가 운영 서버에 도착하기 전에 발견됐다는 뜻입니다. 중요한 것은 빨간 체크를 무시하고 수동 배포하는 것이 아니라, 로그에서 처음 실패한 명령과 오류 위치를 찾아 수정한 뒤 다시 푸시하는 것입니다.

# 오류 수정 후
git add .
git commit -m "fix: 회원가입 타입 오류 수정"
git push origin main

새 커밋이 올라가면 CI는 처음부터 다시 실행됩니다. 이전 실패 로그와 새 성공 결과가 커밋 단위로 남기 때문에 언제부터 문제가 생겼고 어떤 수정으로 해결됐는지도 추적하기 쉽습니다.

main에 push할 때만 검사하면 충분할까

직접 푸시한 뒤 검사하는 것만으로도 수동 검사보다 낫지만, 실패한 코드가 이미 main에 들어간 다음 발견된다는 한계가 있습니다. 팀에서는 보통 기능 브랜치에 푸시하고 Pull Request를 열 때 CI를 실행합니다.

feature/signup 브랜치 push
↓
Pull Request 생성
↓
CI 성공 + 코드 리뷰
↓
main 병합
↓
배포 워크플로 실행

여기에 브랜치 보호 규칙을 적용해 CI 상태 검사를 필수로 지정하면 실패한 Pull Request는 병합할 수 없습니다. GitHub 공식 문서에서도 보호 브랜치의 필수 상태 검사는 병합 전에 통과해야 한다고 설명합니다. 관련 설정은 상태 검사와 보호 브랜치 문서를 참고하면 됩니다.

처음부터 너무 많은 검사를 넣지 않아도 된다

CI를 처음 도입할 때 보안 스캔, 여러 운영체제, 여러 Node.js 버전, E2E 테스트까지 한꺼번에 넣으면 느리고 관리하기 어려운 워크플로가 될 수 있습니다. 작은 프로젝트라면 다음 네 단계로 시작해도 충분합니다.

잠금 파일 기준 의존성 설치

타입 검사와 린트

핵심 기능 테스트

프로덕션 빌드

이후 실제로 반복되는 실수에 맞춰 검사를 추가합니다. 데이터베이스 스키마 누락이 잦다면 스키마 검증을, 포맷 차이로 리뷰가 소모된다면 포맷 검사를, 배포 뒤 기본 화면이 깨진다면 스모크 테스트를 더하는 방식입니다.

CI를 운영할 때 자주 생기는 실수

로컬과 CI의 런타임 버전이 다름: Node.js 버전을 명시하고 운영 환경과 맞춥니다.

잠금 파일을 커밋하지 않음: npm ci가 재현할 기준이 사라집니다.

CI에서만 사용하는 비밀값을 코드에 작성함: 저장소에 넣지 말고 Actions의 암호화된 비밀 저장소를 사용합니다.

검사가 너무 느림: 빠른 정적 검사부터 실행하고 캐시·병렬 작업을 단계적으로 적용합니다.

실패해도 배포함: CI와 배포의 의존 관계를 명확히 하고 필수 검사를 우회하지 않습니다.

워크플로 자체를 검토하지 않음: 외부 액션 버전과 권한 범위도 애플리케이션 코드처럼 리뷰합니다.

마무리

CI의 핵심은 거대한 자동화 시스템이 아닙니다. git push라는 평소 행동 뒤에 팀이 매번 지켜야 할 검사를 붙이는 것입니다. 사람이 기억에 의존해 “타입 검사했나요?”, “빌드해 봤나요?”를 확인하는 대신 저장소가 같은 질문을 모든 커밋에 반복합니다.

작게 시작한다면 오늘 바로 .github/workflows/ci.yml을 만들고, 프로젝트에서 이미 사용하는 타입 검사·린트·테스트·빌드 명령을 연결해 보세요. 첫 번째 빨간 체크가 나타나면 CI가 실패한 것이 아니라, 운영 환경보다 먼저 문제를 발견하는 데 성공한 것입니다.