GitLab CI/CD — artifacts vs cache
GitLab CI에서 가장 자주 혼동되는 artifacts와 cache의 차이를 개념부터 실전 설정까지 한 번에 정리합니다.
Seobway · · 12분
GitLab CI/CD — artifacts vs cache
GitLab CI에서 가장 자주 혼동되는 두 개념.
잘못 쓰면 빌드가 느려지거나, 파일이 다음 잡에 전달되지 않는 문제가 생깁니다.
개념부터 실전 설정까지 한 번에 정리합니다.
1. 왜 헷갈리나 — 근본 원인부터
GitLab CI의 각 잡은 독립된 컨테이너에서 실행됩니다.
잡 A에서 만든 파일은 잡 B에서 자동으로 보이지 않습니다.
%% desc: 잡 간 파일시스템 격리 — 컨테이너 A의 파일이 컨테이너 B에서 보이지 않는 이유
flowchart LR
subgraph JOB_A["build 잡 (컨테이너 A)"]
A_FS["파일시스템\n/builds/project/..."]
A_OUT["dist/app.js ✅ 생성됨"]
A_FS --> A_OUT
end
subgraph JOB_B["deploy 잡 (컨테이너 B)"]
B_FS["파일시스템\n/builds/project/..."]
B_NOPE["dist/app.js ❌ 없음!\n(다른 컨테이너)"]
B_FS --> B_NOPE
end
JOB_A -.->|"기본적으로 공유 안 됨"| JOB_B
이 문제를 해결하는 두 가지 방법이 artifacts와 cache입니다.
하지만 용도가 다릅니다.
2. 핵심 차이 한눈에 보기
artifacts |
cache |
|
|---|---|---|
| 목적 | 잡 간 파일 전달 | 의존성 재다운로드 방지 |
| 방향 | 같은 파이프라인 내 잡 → 잡 | 파이프라인 → 파이프라인 |
| 저장 위치 | GitLab 서버 | Runner 캐시 스토리지 (로컬/S3) |
| 보장 | ✅ 항상 다음 잡에 전달 | ⚠️ 캐시 미스 가능 (있으면 쓰는 것) |
| 만료 | expire_in 으로 설정 |
수동 삭제 또는 cache:key 갱신 |
| 대표 사용처 | 빌드 산출물, 테스트 리포트, 커버리지 | node_modules, .gradle, ~/.pip |
3. artifacts — 잡 간 파일 전달
개념
같은 파이프라인 안에서 앞 잡의 결과물을 뒷 잡이 받아야 할 때 씁니다.
build 잡에서 만든 .jar, .js, 테스트 리포트 등을 deploy/test 잡에서 써야 하는 상황이 대표적입니다.
흐름
%% desc: artifacts 흐름 — GitLab 서버를 통해 빌드 산출물을 test/deploy 잡에 전달
flowchart LR
subgraph PIPE["파이프라인 #1"]
direction LR
subgraph JOB_A["build 잡"]
A1["소스코드 빌드"]
A2["dist/app.js\nbuild/libs/*.jar"]
A1 --> A2
end
subgraph GL["GitLab 서버\nArtifacts 저장소"]
GL_STORE[("artifacts\n압축 저장\nexpire_in 적용")]
end
subgraph JOB_B["test 잡"]
B1["artifacts\n자동 다운로드"]
B2["테스트 실행\n(빌드 결과물 사용)"]
B1 --> B2
end
subgraph JOB_C["deploy 잡"]
C1["artifacts\n자동 다운로드"]
C2["배포 실행"]
C1 --> C2
end
A2 -->|"업로드"| GL_STORE
GL_STORE -->|"다음 잡 시작 시\n자동 다운로드"| B1
GL_STORE -->|"다음 잡 시작 시\n자동 다운로드"| C1
end
yml 설정
stages:
- build
- test
- deploy
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/ # 이 경로를 다음 잡에 전달
expire_in: 1 hour # GitLab 서버에서 1시간 후 삭제
test:
stage: test
needs: [build] # build artifacts 자동 수신
script:
- npm test # dist/ 파일 사용 가능
deploy:
stage: deploy
needs: [build] # build artifacts 자동 수신
script:
- ./deploy.sh dist/
알아두면 좋은 것들
%% desc: artifacts 심화 옵션 — expire_in / when / reports / needs 설정 가이드
flowchart TD
ROOT["artifacts 심화"]
ROOT --> EXP["expire_in\n1 hour / 1 day / 1 week\n설정 안 하면 기본값 (30일)"]
ROOT --> WHEN["when: always\n실패한 잡의 산출물도 보존\n→ 실패 로그, 스크린샷 수집에 유용"]
ROOT --> REPORT["artifacts: reports:\njunit, coverage, dotenv 등\nGitLab UI에서 바로 확인 가능"]
ROOT --> NEEDS["needs: []\n빈 배열이면 artifacts 안 내려옴\n주의 필요"]
artifacts: reports: 는 특히 강력합니다.
test:
script:
- pytest --junitxml=report.xml --cov-report=xml
artifacts:
reports:
junit: report.xml # MR에서 테스트 결과 표시
coverage_report:
coverage_format: cobertura
path: coverage.xml # MR에서 커버리지 표시
4. cache — 파이프라인 간 의존성 재사용
개념
매번 npm install / pip install / ./gradlew dependencies 를 처음부터 하는 시간을 줄이기 위해 씁니다.
파이프라인이 달라도 같은 브랜치라면 이전 캐시를 재사용합니다.
중요: cache는 "있으면 쓰고, 없으면 새로 시작"입니다.
캐시 미스가 나도 파이프라인은 실패하지 않습니다.
흐름
%% desc: cache 파이프라인 흐름 — 첫 번째(MISS)→저장, 두 번째(HIT)→재사용
flowchart TB
subgraph PIPE1["파이프라인 #1 (첫 푸시)"]
P1["build 잡"]
P1A["캐시 복원 시도\n→ cache MISS ❌\n(처음이라 없음)"]
P1B["npm ci\n패키지 전체 다운로드\n(느림 ⏳)"]
P1C["node_modules/ 캐시 저장"]
P1 --> P1A --> P1B --> P1C
end
subgraph RS["Runner 캐시 스토리지\n(key: main-{lock-hash})"]
CACHE[("node_modules/\n압축 저장")]
end
subgraph PIPE2["파이프라인 #2 (다음 푸시)"]
P2["build 잡"]
P2A["캐시 복원 시도\n→ cache HIT ✅"]
P2B["npm ci\n변경분만 처리\n(빠름 ⚡)"]
P2C["node_modules/ 캐시 갱신"]
P2 --> P2A --> P2B --> P2C
end
P1C -->|"저장"| CACHE
CACHE -->|"복원"| P2A
P2C -->|"갱신"| CACHE
yml 설정
build:
cache:
key:
files:
- package-lock.json # 락파일이 바뀌면 캐시 키도 바뀜 → 자동 무효화
paths:
- node_modules/
script:
- npm ci
- npm run build
cache key 전략
캐시 키를 어떻게 잡느냐에 따라 캐시 효율이 크게 달라집니다.
%% desc: cache key 전략 — 브랜치/락파일/전역/복합 키 비교
flowchart TD
ROOT["cache key 전략"]
ROOT --> K1["key: $CI_COMMIT_REF_SLUG\n브랜치별 캐시\n→ 브랜치마다 독립 캐시 유지"]
ROOT --> K2["key:\n files:\n - package-lock.json\n락파일 해시 기반\n→ 의존성 변경 시 자동 무효화"]
ROOT --> K3["key: global\n모든 브랜치 공유\n→ 저장소 절약, 충돌 주의"]
ROOT --> K4["key: $CI_COMMIT_REF_SLUG-node\n브랜치 + 런타임 조합\n→ 다중 언어 프로젝트에 유용"]
락파일 기반 키 가 가장 실전적입니다.
cache:
key:
files:
- package-lock.json # 이 파일의 해시가 키
prefix: "$CI_COMMIT_REF_SLUG" # + 브랜치명 조합
paths:
- node_modules/
5. 같이 쓰는 실전 패턴
artifacts와 cache는 같이 쓰는 게 정석입니다.
%% desc: artifacts + cache 병용 패턴 — build 잡에서 캐시 복원 후 빌드, artifacts 업로드, 캐시 저장
flowchart TB
subgraph PIPE["파이프라인"]
direction TB
subgraph BUILD["build 잡"]
B_CACHE["① cache 복원\nnode_modules/ (빠른 install)"]
B_INSTALL["npm ci"]
B_BUILD["npm run build"]
B_ART["② artifacts 업로드\ndist/ (다음 잡 전달)"]
B_CSAVE["③ cache 저장\nnode_modules/ (다음 파이프라인용)"]
B_CACHE --> B_INSTALL --> B_BUILD --> B_ART
B_BUILD --> B_CSAVE
end
subgraph TEST["test 잡"]
T_ART["artifacts 다운로드\ndist/ 수신"]
T_RUN["테스트 실행"]
T_ART --> T_RUN
end
subgraph DEPLOY["deploy 잡"]
D_ART["artifacts 다운로드\ndist/ 수신"]
D_RUN["배포 실행"]
D_ART --> D_RUN
end
end
subgraph STORES["저장소"]
GL[("GitLab 서버\nArtifacts")]
RS[("Runner 캐시\nnode_modules")]
end
B_ART -->|"업로드"| GL
GL -->|"다운로드"| T_ART
GL -->|"다운로드"| D_ART
B_CSAVE -->|"저장"| RS
RS -->|"다음 파이프라인\n복원"| B_CACHE
실전 yml 예시
stages:
- build
- test
- deploy
build:
stage: build
cache:
key:
files:
- package-lock.json
paths:
- node_modules/ # ← cache: 파이프라인 간 재사용
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/ # ← artifacts: 다음 잡에 전달
expire_in: 1 hour
test:
stage: test
needs: [build] # dist/ 자동 수신
script:
- npm test
artifacts:
when: always # 실패해도 리포트 보존
reports:
junit: test-results.xml
deploy:
stage: deploy
needs: [build] # dist/ 자동 수신
script:
- ./deploy.sh dist/
6. 흐름 비교 — 한 장으로 끝내기
%% desc: artifacts vs cache 흐름 비교 — 같은 파이프라인(artifacts) vs 파이프라인 간(cache)
flowchart LR
subgraph ART["artifacts — 같은 파이프라인, 잡 → 잡"]
direction LR
A1["잡 A\n빌드 산출물 생성"] -->|"항상 업로드 ✅"| AGL[("GitLab 서버")]
AGL -->|"다음 잡 자동 다운로드"| A2["잡 B\n산출물 사용"]
end
subgraph CAC["cache — 파이프라인 → 파이프라인"]
direction LR
C1["파이프라인 #1\n의존성 설치 후 저장"] -->|"캐시 저장"| CRS[("Runner 캐시")]
CRS -->|"복원 (캐시 히트 시만) ⚠️"| C2["파이프라인 #2\n의존성 재사용"]
end
7. 실전 판단 플로차트
%% desc: 실전 판단 플로차트 — 상황별 artifacts/cache/병용 선택 가이드
flowchart TD
Q["이 파일, 어떻게 공유할까?"]
Q -->|"같은 파이프라인의\n다른 잡에서 써야 함"| ART
Q -->|"다음 파이프라인에서\n재설치 시간 줄이고 싶음"| CAC
Q -->|"둘 다 해당\n(빌드 산출물 + 의존성)"| BOTH
ART["✅ artifacts\npath: 빌드 결과물\nexpire_in: 보관 기간"]
CAC["✅ cache\nkey: 락파일 해시\npath: node_modules 등"]
BOTH["✅ 같이 사용\nartifacts: dist/\ncache: node_modules/"]
ART --> SUB_ART["언제 받나?\nneeds: [이전잡] → 자동\nneeds 없어도 같은 stage면 자동"]
CAC --> SUB_CAC["캐시 미스나도 괜찮나?\n✅ 괜찮음\n처음부터 다시 설치할 뿐"]
BOTH --> SUB_BOTH["순서?\n① cache 복원 (의존성)\n② 빌드 실행\n③ artifacts 업로드 (산출물)\n④ cache 저장 (의존성)"]
8. 자주 하는 실수 모음
artifacts인데 cache 씀 (또는 반대)
%% desc: 잘못된 패턴 vs 올바른 패턴 — 잡 간 전달에 cache 대신 artifacts 사용해야 하는 이유
flowchart LR
subgraph WRONG["❌ 잘못된 패턴 — cache 사용"]
direction TB
W1["build 잡\ncache: dist/ 저장"]
W2["deploy 잡\n→ dist/ 없음!\n캐시 미스 또는 다른 Runner"]
W1 -.->|"보장 안 됨 ⚠️"| W2
end
subgraph RIGHT["✅ 올바른 패턴 — artifacts 사용"]
direction TB
R1["build 잡\nartifacts: dist/ 저장"]
R2["deploy 잡\n→ dist/ 항상 있음 ✅"]
R1 -->|"artifacts 보장"| R2
end
WRONG ~~~ RIGHT
needs: [] 로 artifacts 차단
# ❌ 이렇게 하면 build artifacts 안 내려옴
deploy:
needs: [] # 빈 배열 = "아무 잡도 기다리지 않겠다" = artifacts도 없음
# ✅ 올바른 방법
deploy:
needs: [build] # build 잡의 artifacts 자동 수신
캐시 키 너무 넓게 잡기
# ❌ 모든 브랜치가 같은 캐시 → 충돌/오염 위험
cache:
key: "global"
paths:
- node_modules/
# ✅ 브랜치 + 락파일 조합
cache:
key:
files:
- package-lock.json
prefix: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
9. 한 줄 요약
| 한 줄 요약 | |
|---|---|
artifacts |
"이 파일, 다음 잡에 줘" — 같은 파이프라인 내 잡 간 전달, 보장됨 |
cache |
"이 폴더, 다음 파이프라인에서 재사용" — 설치 시간 단축, 미스 가능 |
- GitLab
- CI/CD
- DevOps
- artifacts
- cache
- 인프라