2025년형 GitHub Actions CI/CD 파이프라인: “빠르게”가 아니라 “안전하게 자동화”하는 방법
들어가며
2025년의 CI/CD는 “빌드/테스트 자동화”를 넘어서 배포 권한 통제, 공급망 보안, 병렬 실행 제어, 캐시 전략까지 한 덩어리로 설계해야 합니다. GitHub Actions는 YAML 몇 줄로 시작할 수 있지만, 실제 운영에서는 작은 설정 하나(예: permissions, concurrency, cache 버전) 때문에 배포 충돌, 토큰 과권한, 캐시 실패로 인한 전면 장애가 나기도 합니다.
최근 변화 중 특히 실무에 영향이 큰 건 캐시입니다. GitHub Actions 캐시 백엔드가 v2로 전환되며, @actions/cache 패키지는 2025-02-01부터 구버전이 사실상 실패를 유발할 수 있으니 v4+로 업그레이드 권고가 공지되었습니다. 즉, “예전 YAML 그대로”는 2025년에 더 위험합니다. (github.com)
🔧 핵심 개념
1) 파이프라인을 3층으로 나눠라: CI / CD / Governance
- CI(검증): lint/test/build, artifact 생성. 빠르고 반복 가능해야 함.
- CD(배포): environment 별로 승인/제한/비밀값 접근이 달라짐.
- Governance(통제): 누가 워크플로를 바꿀 수 있는지, 어떤 Action을 허용하는지, 토큰 권한을 어디까지 주는지.
이 3층을 섞어버리면 “CI 수정”이 “프로덕션 배포 권한 변경”으로 이어지는 사고가 발생합니다. GitHub는 이를 방지하려고 GITHUB_TOKEN 권한 최소화, 환경 보호 규칙, OIDC 등을 강하게 권장합니다. (docs.github.com)
2) GITHUB_TOKEN과 permissions: 기본값에 기대지 말 것
GitHub Actions에서 대부분의 자동화는 GITHUB_TOKEN으로 돌아갑니다. 중요한 포인트는:
- Action은 명시적으로 토큰을 넘기지 않아도
github.token컨텍스트로 토큰에 접근 가능 - 그래서 workflow/job 단위로
permissions를 최소 권한으로 고정하는 게 안전합니다 (docs.github.com)
3) OIDC로 “장기 Secret”을 제거하라
클라우드 배포에서 가장 흔한 안티패턴은 AWS_ACCESS_KEY_ID 같은 장기 키를 secrets에 넣는 것입니다. GitHub Actions는 OIDC(OpenID Connect) 로 워크플로 실행 시점에만 유효한 토큰을 발급받아 클라우드에 로그인하도록 설계할 수 있습니다. 이를 위해 워크플로에 permissions: id-token: write가 필요합니다. (docs.github.com)
4) concurrency로 “중복 배포”를 구조적으로 차단
main 브랜치에 커밋이 연속으로 들어오면, 이전 배포가 끝나기도 전에 새 배포가 시작되며 환경이 꼬일 수 있습니다. GitHub Actions의 concurrency는 같은 그룹의 실행을 대기/취소시켜 “마지막 커밋만 배포” 같은 정책을 쉽게 구현합니다. (docs.github.com)
5) 캐시는 “성능”이 아니라 “신뢰성” 문제
캐시는 단순 가속 장치가 아니라, 잘못 설계하면 “오염된 결과를 빠르게 재현”하는 장치입니다.
- 키 설계: lockfile hash 기반 + restore-keys로 점진적 폴백
- 보안: 캐시에 민감 정보 저장 금지(특히 PR에서 악용 가능) (docs.github.com)
- 2025 관점:
actions/cache@v4사용이 사실상 필수 레벨 (docs.github.com)
💻 실전 코드
아래 예시는 Node.js(예: React/Next/Nest 상관없음) 기준으로, “CI(검증) + CD(배포)”를 한 파일에서 보여주되, 운영에서는 CD를 별 workflow로 분리하거나 reusable workflow로 쪼개는 것을 추천합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
name: ci-cd
on:
pull_request:
push:
branches: [ "main" ]
# 같은 브랜치/워크플로에서 여러 실행이 겹치면, 최신 실행만 남기고 취소
concurrency:
group: $-$
cancel-in-progress: true
# 기본은 최소 권한. (job별로 추가 권한 부여)
permissions:
contents: read
jobs:
ci:
name: CI (lint/test/build)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
# setup-node 내장 캐시를 쓰는 방법도 있지만,
# 여기서는 cache action을 명시해서 키/정책을 통제한다.
# cache: "npm"
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
# lockfile이 바뀌면 새 캐시 생성 (재현성)
key: npm-$-$
restore-keys: |
npm-$-
- name: Install
run: npm ci
- name: Test
run: npm test --if-present
- name: Build
run: npm run build --if-present
deploy:
name: CD (deploy to prod)
needs: [ "ci" ]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
# OIDC로 클라우드 접근하려면 id-token: write 필요
permissions:
contents: read
id-token: write
# 환경 보호 규칙(승인자/브랜치 제한/대기시간 등)을 여기에 연결
environment: production
steps:
- name: Checkout
uses: actions/checkout@v5
# 예시: OIDC 토큰을 직접 "요청"만 해서 존재 확인 (클라우드별 로그인 액션은 각자 다름)
- name: Prove OIDC token can be issued
uses: actions/github-script@v7
with:
script: |
const core = require('@actions/core');
// id-token: write 권한이 있어야 정상 동작
const token = await core.getIDToken('my-audience');
core.info(`OIDC token issued (length=${token.length})`);
- name: Deploy (placeholder)
run: |
echo "여기서 AWS/Azure/GCP 공식 로그인 action으로 OIDC 교환 후 배포하세요."
echo "예: kubectl apply, terraform, serverless deploy 등"
이 구성의 핵심은 “기능”이 아니라 통제 지점입니다.
- CI는
contents: read로 고정 - CD는
id-token: write만 추가(장기 secret 제거 방향) (docs.github.com) concurrency로 main 배포 경합을 구조적으로 제거 (docs.github.com)- 캐시는
actions/cache@v4기반으로 키를 lockfile에 결박 (docs.github.com)
⚡ 실전 팁
1) permissions는 “명시”가 정책이다
- “작동하니까 OK”가 아니라, 어떤 자동화가 어떤 권한으로 움직이는지가 CI/CD의 품질입니다.
- 특히 PR에서 실행되는 workflow는 더 보수적으로(예:
pull_request에서 write 권한 금지) 설계하세요. (docs.github.com)
2) 배포는 environment로 감싸고, 보호 규칙을 적극 활용
- production 배포는
environment: production에 연결하고, GitHub UI에서 승인자(required reviewers)나 브랜치 제한 같은 보호 규칙을 두는 방식이 운영 친화적입니다. - OIDC 정책 조건에 environment를 엮으면 “특정 환경에서만 토큰 발급” 같은 통제가 가능해집니다. (docs.github.com)
3) 캐시 키는 “정확도 우선”, restore-keys는 “속도 우선”
key는 lockfile hash로 재현성을 확보하고restore-keys는 부분 매칭으로 속도를 챙깁니다.- 그리고 캐시에 credential/토큰/빌드 산출물 중 민감 데이터가 섞이지 않게 경로를 엄격히 제한하세요. (docs.github.com)
4) 2025 캐시 이슈 체크리스트
actions/cache@v4사용 여부 확인(조직 내 템플릿/내부 Action 포함)- 오래된
@actions/cache의존(커스텀 JS Action) 있으면 v4+로 업그레이드 계획 수립 (github.com)
5) 중복 실행 제어는 “비용”이 아니라 “사고 방지”
concurrency로 main 배포를 직렬화하고cancel-in-progress: true로 “최신만 배포”를 구현하면, 배포 충돌/리소스 낭비/롤백 난이도가 크게 줄어듭니다. (docs.github.com)
🚀 마무리
2025년 GitHub Actions로 CI/CD를 “잘” 만든다는 건 YAML을 많이 아는 게 아니라,
permissions로 최소 권한- OIDC로 장기 secret 제거
concurrency로 배포 경합 제거actions/cache@v4+ 올바른 키 설계로 속도와 재현성 균형 을 시스템적으로 설계하는 것입니다. (docs.github.com)
다음 학습 추천:
- GitHub Actions OIDC를 실제 클라우드(AWS/Azure/GCP) 로그인 action과 연결해 “무비밀 배포” 완성 (docs.github.com)
- Security hardening 가이드 기반으로 Action pinning/승인 흐름(CODEOWNERS 포함)까지 파이프라인 거버넌스로 확장 (docs.github.com)