[CI] turborepo를 통해서 모노레포 CI를 어떻게 만들까? vercel에서 만든 turborepo를 바탕으로 알아보자.

2026. 6. 30. 21:07·devops

[서론]

turborepo로 monorep를 빌드시스템을 어떻게 최적화할까? 에 대해 알아보았다.

이걸 바탕으로 CI는 어떻게 만드는지 확인해보자.

 

저번 글


 

[Turborepo CI/CD를 위한 영향도 개념 확인하기]

turbo query affected를 기준으로, 변경된 코드가 어떤 앱과 태스크에 영향을 주는지 확인하고, 이를 GitHub Actions와 연결하는 방법을 정리할 예정이다.

[1. turbo query affected가 무엇인가?]

turbo query affected는 두 Git ref 사이의 변경 사항을 기준으로, 이번 변경에 영향을 받는 패키지 또는 태스크를 조회하는 명령어다.

 

기본 개념은 단순하다. Git 기준 브랜치와 현재 브랜치 사이의 변경 파일을 찾고, 그 파일이 어느 workspace package에 속하는지 확인한 뒤, package graph를 따라 변경 영향이 도달하는 패키지를 계산한다.이후 turbo.json에 정의된 task graph를 기준으로 실제로 실행 대상이 되는 task를 확인한다.

 

즉, affected는 “변경된 파일이 있는 워크스페이스만 찾는 기능”이 아니라, 변경된 패키지와 그 패키지를 의존하는 패키지까지 포함해 CI에서 검증해야 할 실행 범위를 계산하는 기능이다.

Git diff
  ↓
Changed files
  ↓
Changed packages
  ↓
Package Graph [package.json + lockfile + workspace 기반 graph]
  ↓
Affected packages
  ↓
Task Graph [turbo.json 기반 graph]
  ↓
Affected tasks
  ↓
CI 실행 대상

[2. Package Graph란 무엇인가?]

Turborepo 공식 문서에서는 Package Graph를 “패키지 매니저가 만든 모노레포 구조”라고 설명한다. Package Graph는 모노레포 안의 패키지들이 서로 어떤 의존 관계를 가지고 있는지를 나타내는 그래프다.

 

즉, Turborepo가 임의로 코드를 분석해서 그래프를 만드는 것이 아니라, package.json, workspace 설정, lockfile을 바탕으로 패키지 간 의존 관계를 이해한다. 내부 패키지들이 서로를 설치하면 Turborepo는 그 의존 관계를 자동으로 식별한다. (turborepo)

 

앞에서 워크스페이스를 잘 정리했기 때문에 Package Graph가 가능해진다.

 

예를 들어 apps/web/package.json에 다음과 같은 의존성이 있다고 하자.

{
  "name": "web",
  "dependencies": {
    "@repo/app-shell": "workspace:*",
    "@repo/core": "workspace:*",
    "@repo/shared-ui": "workspace:*",
    "@repo/ui": "workspace:*"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@repo/next-config": "workspace:*",
    "@repo/tailwind-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@repo/vitest-config": "workspace:*"
  }
}

이 선언을 바탕으로 Turborepo는 web이 어떤 내부 패키지를 사용하는지 알 수 있다.

web
  ├─ depends on @repo/app-shell
  ├─ depends on @repo/core
  ├─ depends on @repo/shared-ui
  ├─ depends on @repo/ui
  ├─ depends on @repo/eslint-config
  ├─ depends on @repo/next-config
  ├─ depends on @repo/tailwind-config
  ├─ depends on @repo/typescript-config
  └─ depends on @repo/vitest-config

여기서 중요한 점은 workspace:*이다. 이는 외부 npm registry에서 패키지를 가져오는 것이 아니라, 같은 모노레포 안에 있는 내부 패키지를 참조한다는 의미다. 따라서 web이 @repo/ui를 dependencies로 선언하고 있다면, Turborepo는 다음과 같은 연결을 알 수 있다.

web → @repo/ui

이 연결이 바로 affected 판단의 근거가 된다.

예를 들어 packages/ui/src/button.tsx가 변경되었다고 하자. 이 파일은 @repo/ui 패키지에 속한다. 그런데 web은 @repo/ui를 의존하고 있다. 따라서 Turborepo는 @repo/ui 변경이 web에도 영향을 줄 수 있다고 판단한다.

packages/ui/src/button.tsx 변경
  ↓
@repo/ui 패키지 변경
  ↓
web이 @repo/ui를 의존함
  ↓
web도 affected package 후보

즉, Package Graph는 “어떤 패키지가 어떤 패키지를 사용하는가?”를 표현하는 구조다. 그리고 affected는 변경된 패키지를 이 Package Graph 위에 전파해서, 변경의 영향을 받을 수 있는 패키지까지 찾아낸다.

Turborepo의 --filter 문서에서도 ... 문법은 Package Graph를 기준으로 대상 패키지의 dependents 또는 dependencies를 선택한다고 설명한다. 패키지 이름 앞에 ...를 붙이면 해당 패키지에 의존하는 패키지들이 선택되고, 패키지 이름 뒤에 ...를 붙이면 해당 패키지가 의존하는 패키지들이 선택된다. (turborepo)

예를 들어 개념적으로는 다음과 같이 볼 수 있다.

# @repo/ui에 의존하는 패키지들 선택
turbo run build --filter=...@repo/ui

# web이 의존하는 패키지들까지 포함
turbo run build --filter=web...

 

따라서 Package Graph를 이해하면 affected가 왜 특정 앱을 실행 대상으로 선택하는지 이해할 수 있다. 단순히 파일 경로가 바뀌었기 때문이 아니라, 변경된 패키지와 다른 패키지 사이의 의존 관계가 존재하기 때문에 build의 실행 대상이 되는 것이다.

 

[Turborepo 기반 CI 구성하기]

[turbo query affected와 CI 연결하고 GitHub Actions 만들기]

이제 turbo query affected를 GitHub Actions에 연결해보자.

CI에서 핵심은 두 단계다.

turbo query affected
# 이번 변경으로 어떤 패키지/태스크가 영향을 받았는지 조회

turbo run build lint test --affected
# 영향을 받은 패키지에 대해서만 실제 CI 태스크 실행

즉, turbo query affected는 확인용이고, turbo run --affected는 실행용이다.

turbo query affected
  ↓
affected 대상 확인

turbo run build lint test --affected
  ↓
affected 패키지만 build / lint / test 실행

Turborepo 공식 문서에 따르면 --affected는 현재 브랜치 변경 사항에 영향을 받은 패키지만 필터링한다. 기본적으로는 main...HEAD 사이의 Git 변경 사항을 기준으로 동작하며, --filter와 함께 쓰면 두 조건을 모두 만족하는 패키지만 선택된다. 또한 checkout이 너무 shallow하면 모든 패키지가 변경된 것으로 간주될 수 있으므로 CI에서는 Git 히스토리를 충분히 가져와야 한다.

https://turborepo.dev/docs/reference/run#--affected

 

GitHub Actions 예시는 다음과 같다.

name: CI

on:
  pull_request:
    types: [opened, synchronize, reopened]
  push:
    branches:
      - main

jobs:
  ci:
    name: Build, Lint, Test
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Check out repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Install dependencies
        run: bun install --frozen-lockfile

      - name: Show affected tasks
        run: bunx turbo query affected

      - name: Run CI for affected packages
        run: bunx turbo run build lint test --affected

이 workflow의 흐름은 다음과 같다.

PR 또는 main push 발생
  ↓
repository checkout
  ↓
의존성 설치
  ↓
turbo query affected로 영향 받은 태스크 확인
  ↓
turbo run build lint test --affected 실행
  ↓
변경 영향이 있는 패키지만 CI 검증

 

여기서 중요한 설정은 fetch-depth: 0이다.

with:
  fetch-depth: 0

affected는 Git diff를 기준으로 판단하기 때문에, 기준 브랜치와 현재 브랜치 사이의 Git 이력이 필요하다. Git 히스토리가 부족하면 Turborepo가 변경 범위를 정확히 계산하지 못하고 모든 패키지를 변경된 것으로 판단할 수 있다.

특정 앱만 확인하고 싶다면 --filter를 함께 사용할 수 있다.

turbo run build --affected --filter=web

이 명령어는 web 패키지가 affected일 때만 web#build를 실행한다. 공식 문서에서도 --affected와 --filter를 함께 사용하면 두 조건을 모두 만족하는 패키지만 선택된다고 설명한다.

따라서 기본 CI에서는 다음처럼 단순하게 시작하면 된다.

bunx turbo query affected
bunx turbo run build lint test --affected

이후 앱별 배포까지 연결해야 한다면, --filter=web, --filter=admin처럼 앱 단위로 조건을 좁히면 된다.


[potato의 후기!]

 

놀랍게 위에 나온 시간이 turborepo를 이용한 ci다. 오히려 속도가 더 느려졌다. 

 

기존 ci.yml = runner-level 병렬화였고 turbo-ci.yml = 한 runner 안 task-level 병렬화였기 대문이다. 
dependsOn: ["^build"] 이 한 명령 안에서 Turbo가 여러 task graph를 같이 계산한다.
 
의존성 때문에 일부 작업이 묶이기 때문에 속도가 느려졌다.
규모에 따라서 CI는 조정할 필요가 있어 보인다. 다음에는 CD 작업을 진행 해봐야겠다.
 

 

그리고 잘못된 사항이나 질문사항 있으시면 질문 남겨주세요. 답변 드리겠습니다~ 감사합니다~


[참고자료]

 

[package graph] : https://turborepo.dev/docs/core-concepts/package-and-task-graph

저작자표시 비영리 변경금지 (새창열림)

'devops' 카테고리의 다른 글

[AI 시대 개발자가 알아야할 인프라 구성] CH 2. 환경 구성 (GCP, Claude Code, Github 레츠고)  (1) 2026.07.05
[AI 시대 개발자가 알아야할 인프라 구성] CH 1. AI 시대, 개발자의 인프라  (0) 2026.07.05
[monorepo] 모노레포의 빌드 시스템을 최적화하는 Turborepo란 무엇인가? vercel에서 만든 Turborepo를 바탕으로 알아보자.  (0) 2026.06.29
[HTTPS] HTTP와 TLS의 결합인 HTTPS(Hyper Text Transfer Protocol Secure)란 무엇인가? RFC 2818 HTTP Over TLS를 바탕으로 알아보자  (0) 2026.06.25
[TLS] SSL(Secure Sockets Layer)에서 진화한 TLS(Transport Layer Security)란 무엇인가? 클라우드 플레어를 통해서 알아보자  (0) 2026.06.24
'devops' 카테고리의 다른 글
  • [AI 시대 개발자가 알아야할 인프라 구성] CH 2. 환경 구성 (GCP, Claude Code, Github 레츠고)
  • [AI 시대 개발자가 알아야할 인프라 구성] CH 1. AI 시대, 개발자의 인프라
  • [monorepo] 모노레포의 빌드 시스템을 최적화하는 Turborepo란 무엇인가? vercel에서 만든 Turborepo를 바탕으로 알아보자.
  • [HTTPS] HTTP와 TLS의 결합인 HTTPS(Hyper Text Transfer Protocol Secure)란 무엇인가? RFC 2818 HTTP Over TLS를 바탕으로 알아보자
맵시 포테이토
맵시 포테이토
감자하다. 정말.
  • 맵시 포테이토
    기분 감자. 분위기 감자.
    맵시 포테이토
  • 전체
    오늘
    어제
    • 분류 전체보기 (61)
      • python (3)
      • cloud (3)
      • devops (22)
      • 논문 (1)
      • llm (3)
      • 잡다구리 (8)
      • 알고리즘 (1)
      • playground (0)
      • 아카이브 (20)
        • 영상 후기! (11)
        • 책이 끝나고 난 뒤 (4)
        • MOVIE! (3)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    19금영화
    독서
    소설
    스포
    글쓰기
    보안
    통계학입문
    claude code
    소설추천
    데이터사이언티스트
    AWS
    MonoRepo
    독후감
    프로토콜
    공부
    CD
    영화
    vercel
    군대
    데브옵스
    TLS
    넷플릭스
    S3
    모노레포
    넷플릭스 추천
    로맨스코미디
    통계학
    터보레포
    영화 추천
    Turborepo
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
맵시 포테이토
[CI] turborepo를 통해서 모노레포 CI를 어떻게 만들까? vercel에서 만든 turborepo를 바탕으로 알아보자.
상단으로

티스토리툴바