빌드와 테스트 단계를 GitHub Actions YAML에 직접 쓰는 팀이 많습니다. 그 자체로는 잘 돌아가죠. 문제는 같은 빌드를 내 노트북에서 돌려보고 싶어질 때, 혹은 누군가 “GitLab으로 옮기면 어때요?“라고 말하는 순간입니다. 파이프라인 전체가 특정 벤더의 설정 포맷에 용접돼 있다는 걸 그제야 깨닫게 되니까요. 요즘 제 방식은 꽤 심심합니다. 진짜 로직은 Docker Compose에 두고, CI 도구는 그걸 호출하는 역할만 시키는 겁니다.
빌드 로직을 Actions YAML에 넣지 않는 이유#
가장 뻔한 문제는 종속성입니다. Actions 워크플로우는 GitLab, Bitbucket, Jenkins 어디에서도 통하지 않으니, 빌드 단계가 워크플로우 파일 안에 있으면 플랫폼을 바꿀 때 전부 다시 써야 합니다. 그런 작업에 시간을 배정해 주는 조직은 본 적이 없네요.
더 은근하게 괴로운 문제는 파이프라인을 로컬에서 돌릴 수 없다는 점입니다. 빌드 수정을 확인할 방법이 “커밋 푸시하고 Actions 탭 쳐다보기"뿐이면, git 히스토리는 금세 “fix ci”, “fix ci again” 같은 커밋으로 가득 찹니다. Compose 파일은 제 노트북에서나 러너에서나 똑같이 돌아가니, “제 컴퓨터에서는 되는데요” 같은 논쟁도 대부분 시작 전에 끝나고요.
그리고 YAML은 자랍니다. 처음엔 깔끔한 20줄이던 것이 캐싱 트릭과 조건문이 붙으면서 300줄이 되고, 개발자들은 Git에 더해 CI 방언까지 배워야 하죠. 스크립트 하나는 한 가지 일만 해야 하는데, 거대한 워크플로우 파일은 모든 걸 한꺼번에 떠안기 마련입니다.
Compose를 쓰면 이 분담이 깔끔해집니다. 빌드와 테스트는 Compose 파일의 책임, CI 플랫폼은 푸시가 있을 때마다 그걸 실행해 주는 존재일 뿐입니다.
설정 방법#
- 먼저
Dockerfile을 만들어 이미지 빌드 방법을 한곳에 모읍니다:
# 공식 Node.js 런타임을 베이스 이미지로 사용
FROM node:14
# 컨테이너 내 작업 디렉토리 설정
WORKDIR /app
# package.json 및 package-lock.json 파일을 컨테이너로 복사
COPY package*.json ./
# 종속성 설치
RUN npm install
# 나머지 애플리케이션 코드를 컨테이너로 복사
COPY . .
# 앱이 실행되는 포트 노출
EXPOSE 3000
# 애플리케이션을 실행하는 명령 정의
CMD ["npm", "start"]다음으로 빌드와 테스트에 필요한 서비스를 정의하는
docker-compose.build.yaml을 만듭니다.services: app: image: myprivaterepo.com/image:latest build: . volumes: - .:/app command: sh -c "npm install && npm test"로컬에서 먼저 돌려봅니다. 사실 이게 이 방식의 핵심이죠.
docker compose -f docker-compose.build.yaml build여기까지 동작하면 GitHub Actions 워크플로우(다른 CI 플랫폼이라도 마찬가지입니다)는 단순한 오케스트레이터로 줄어듭니다.
name: CI/CD Pipeline on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Build and Test with Docker Compose run: docker compose -f docker-compose.build.yaml build - name: Push the image referenced in the docker compose file run: docker compose -f docker-compose.build.yaml push
이제 CI 도구가 하는 일은 빌드와 푸시, 딱 두 가지입니다. 이해할 가치가 있는 부분은 전부, 어떤 플랫폼에서든 어떤 새 팀원이든 바로 실행해 볼 수 있는 파일 안에 있습니다.
이미지 푸시#
Compose 파일의 반가운 부수 효과 하나는 푸시가 아주 쉬워진다는 겁니다. Docker는 이미지 이름을 보고 푸시할 곳을 판단합니다. image: myprivaterepo.com/image:latest라면 myprivaterepo.com으로 푸시됩니다. 프라이빗 레지스트리라면 액세스 토큰이 필요하니, 그 부분은 사용하는 레지스트리 서비스의 문서를 참고하세요.
지금 GitHub Actions에 만족하고 있다면 서두를 이유는 없습니다. 다만 언젠가 공급자를 바꾸는 날이 오면, 마이그레이션이 “YAML 300줄 이식"이 아니라 “명령어 두 개 복사"로 끝납니다. 그리고 그날이 오기 전까지는, 빌드 디버깅을 커밋 스팸 없이 로컬에서 차분히 할 수 있고요.

