

📌 들어가며
오늘의 학습 목표: CI/CD의 개념을 이해하고, GitHub Actions로 Spring Boot 프로젝트의 빌드 → 테스트 → Docker 이미지 생성 → 배포까지 자동화하기
안녕하세요! 오늘은 지난 Docker 학습의 연장선으로 CI/CD를 공부했습니다.
지난번에 Docker로 프로젝트를 컨테이너화하는 방법을 배웠잖아요? 그런데 실제로 사용해보니 이런 상황이 반복되더라고요:
코드 수정
→ ./gradlew clean build
→ docker build -t my-app:1.1 .
→ docker push your-username/my-app:1.1
→ ssh로 EC2 접속
→ docker pull your-username/my-app:1.1
→ docker compose down
→ docker compose up -d
→ 로그 확인...
...이걸 코드 고칠 때마다? 😇
솔직히 두세 번은 괜찮았는데, 열 번쯤 반복하니까 "이걸 자동화할 수 없나?"라는 생각이 들더라고요.
그래서 찾은 답이 바로 CI/CD! 코드를 push만 하면 빌드부터 배포까지 알아서 해주는 마법 같은 기술이에요. ✨
특히 GitHub Actions는 GitHub 저장소에 내장되어 있어서, 별도 서버 설정 없이 바로 사용할 수 있어 부트캠프 프로젝트에 딱이었습니다!
🎯 Today I Learned
✅ CI/CD란 무엇이고 왜 필요한가?
✅ CI, CD, CD의 차이 (Continuous Integration / Delivery / Deployment)
✅ CI/CD 도구 비교 (GitHub Actions, Jenkins, GitLab CI 등)
✅ GitHub Actions의 핵심 개념 (Workflow, Job, Step, Runner)
✅ YAML 문법 기초
✅ GitHub Actions Workflow 작성법
✅ Spring Boot 프로젝트 자동 빌드 & 테스트
✅ Docker 이미지 자동 빌드 & Docker Hub 푸시
✅ AWS EC2 자동 배포
✅ 환경 변수와 Secrets 관리
✅ 브랜치 전략과 CI/CD 연동
✅ 실전 트러블슈팅
🤔 CI/CD, 왜 필요할까?
수동 배포의 고통

🔥 수동 배포 시나리오 (실화)
금요일 오후 5시, 급한 버그 수정 후 배포해야 하는 상황:
1. 로컬에서 빌드 (2분)
2. 테스트 실행 (3분)
3. Docker 이미지 빌드 (2분)
4. Docker Hub에 푸시 (3분)
5. EC2에 SSH 접속 (1분)
6. 이미지 pull (2분)
7. 기존 컨테이너 중지 (30초)
8. 새 컨테이너 실행 (30초)
9. 로그 확인, 동작 테스트 (3분)
총 소요: 약 17분 😱
이걸 하루에 5번 배포하면?
→ 17분 × 5 = 85분 = 약 1시간 반을 배포에만!
한 달이면?
→ 85분 × 20일 = 1,700분 ≈ 28시간 = 3.5일!!
😇 개발할 시간이 없어...
거기다 수동 배포는 실수하기 쉬워요:
자주 발생하는 실수들:
❌ 테스트 안 돌리고 배포 → 버그가 운영 서버에!
❌ docker build 할 때 태그 잘못 지정 → 옛날 버전 배포
❌ 환경 변수 설정 누락 → 서버에서 에러
❌ 빌드는 했는데 push를 깜빡함 → EC2에서 old 이미지 pull
❌ 새벽에 졸면서 배포 → rm -rf / 같은 실수... 😱
CI/CD가 해결해주는 것
CI/CD 도입 후:
1. 코드 수정
2. git push
3. ☕ 커피 마시기
4. 끝! 🎉
GitHub Actions가 알아서:
✅ 코드 빌드
✅ 테스트 실행
✅ Docker 이미지 빌드
✅ Docker Hub에 푸시
✅ EC2에 배포
✅ 헬스 체크
총 소요: git push 10초 + 자동 파이프라인 5~7분
(그 동안 다른 작업 가능!)
📚 CI/CD 개념 정리
CI (Continuous Integration) - 지속적 통합

CI는 개발자들이 코드를 자주, 작은 단위로 메인 브랜치에 병합하고, 병합할 때마다 자동으로 빌드 & 테스트하는 것이에요.
CI 이전 (전통적 개발):
개발자 A: 2주 동안 feature-A 개발...
개발자 B: 2주 동안 feature-B 개발...
개발자 C: 2주 동안 feature-C 개발...
2주 후, 모든 코드 병합:
→ 💥 충돌 지옥!
→ 🔥 통합에만 3일 소요
→ 😭 "내 코드는 잘 됐는데, 합치니까 안 돼요..."
이것을 "통합 지옥 (Integration Hell)"이라고 해요.
CI 이후:
개발자 A: 작은 기능 완성 → push → 자동 빌드/테스트 ✅
개발자 B: 작은 기능 완성 → push → 자동 빌드/테스트 ✅
개발자 C: 작은 기능 완성 → push → 자동 빌드/테스트 ✅
매일매일 조금씩 통합:
→ ✅ 충돌이 작아서 해결 쉬움
→ ✅ 문제를 즉시 발견
→ ✅ 항상 동작하는 코드 유지
CI의 핵심 원칙:
1️⃣ 자주 커밋하기 (하루에 최소 1번)
2️⃣ 모든 커밋에 자동 빌드 & 테스트
3️⃣ 빌드 실패 시 즉시 수정 (최우선 과제)
4️⃣ 메인 브랜치는 항상 배포 가능한 상태 유지
CD (Continuous Delivery) - 지속적 전달
**CD (Delivery)**는 CI를 통과한 코드를 운영 환경에 배포할 준비가 된 상태로 자동으로 만들어두는 것이에요.
Continuous Delivery:
코드 push
→ 자동 빌드 ✅
→ 자동 테스트 ✅
→ 스테이징 환경에 자동 배포 ✅
→ QA 검증
→ 프로덕션 배포 [수동 버튼 클릭] 🔘
↑
사람이 최종 확인 후 배포!
핵심: 배포할 준비는 항상 되어있고, 언제든 버튼만 누르면 배포 가능!
CD (Continuous Deployment) - 지속적 배포

**CD (Deployment)**는 Delivery에서 한 단계 더 나아가, 프로덕션 배포까지 완전 자동화하는 것이에요.
Continuous Deployment:
코드 push
→ 자동 빌드 ✅
→ 자동 테스트 ✅
→ 스테이징 자동 배포 ✅
→ 자동화된 QA 테스트 ✅
→ 프로덕션 자동 배포 ✅ ← 사람 개입 없음!
CI / CD (Delivery) / CD (Deployment) 비교
┌──────────────────────────────────────────────────────────────────┐
│ │
│ CI (Continuous Integration) │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Code │→ │ Build │→ │ Test │ │
│ │ Push │ │ │ │ │ │
│ └─────────┘ └─────────┘ └──────────┘ │
│ │
│ CD (Continuous Delivery) │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Code │→ │ Build │→ │ Test │→ │ Staging │→ 🔘 │
│ │ Push │ │ │ │ │ │ Deploy │ 수동 │
│ └─────────┘ └─────────┘ └──────────┘ └──────────┘ 배포 │
│ │
│ CD (Continuous Deployment) │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────┐ │
│ │ Code │→ │ Build │→ │ Test │→ │ Staging │→ │Prod│ │
│ │ Push │ │ │ │ │ │ Deploy │ │배포│ │
│ └─────────┘ └─────────┘ └──────────┘ └──────────┘ └────┘ │
│ 모두 자동! 🚀 │
└──────────────────────────────────────────────────────────────────┘
실무에서는?
🏢 대기업/금융: 보통 Continuous Delivery
→ 배포 전 승인 프로세스 필요
→ 규제 준수
🚀 스타트업/SaaS: 보통 Continuous Deployment
→ 빠른 배포가 경쟁력
→ Netflix, Meta, GitHub 등이 사용
📚 부트캠프 프로젝트: Continuous Deployment 추천!
→ 배포 자동화 경험이 포트폴리오에 좋음
→ 빠른 피드백 루프
🔧 CI/CD 도구 비교
주요 CI/CD 도구들

도구 특징 비용 러닝커브 추천 대상
| GitHub Actions | GitHub 내장, YAML 기반 | 무료(2,000분/월) | ⭐⭐ 낮음 | 개인/소규모 팀 |
| Jenkins | 오픈소스, 플러그인 풍부 | 무료(서버 비용) | ⭐⭐⭐⭐ 높음 | 대규모 팀/기업 |
| GitLab CI | GitLab 내장, 올인원 | 무료 플랜 있음 | ⭐⭐⭐ 보통 | GitLab 사용자 |
| CircleCI | 빠른 빌드, Docker 친화적 | 무료 플랜 있음 | ⭐⭐⭐ 보통 | Docker 중심 팀 |
| Travis CI | 오픈소스 친화적 | 유료 전환 | ⭐⭐ 낮음 | 오픈소스 프로젝트 |
| AWS CodePipeline | AWS 생태계 통합 | 종량제 | ⭐⭐⭐ 보통 | AWS 중심 팀 |
왜 GitHub Actions를 선택했나?
GitHub Actions를 선택한 이유:
1️⃣ GitHub에 내장되어 있음
→ 별도 서버 설정 불필요
→ 저장소와 완벽 통합
2️⃣ 무료 사용량이 넉넉함
→ Public repo: 무제한 무료!
→ Private repo: 월 2,000분 무료
→ 부트캠프 프로젝트에 충분
3️⃣ 풍부한 마켓플레이스
→ 다른 개발자가 만든 Action 재사용
→ Docker, AWS, Slack 등 수천 개 Action
4️⃣ 학습 곡선이 낮음
→ YAML만 알면 바로 시작
→ 공식 문서가 잘 되어있음
5️⃣ 실무에서도 많이 사용
→ 포트폴리오에 쓰기 좋음
→ 면접에서 이야기할 거리
Jenkins vs GitHub Actions

Jenkins:
🏠 자체 서버 필요 (EC2에 Jenkins 설치)
🔧 설정이 복잡 (Groovy 스크립트)
🔌 플러그인으로 기능 확장
💰 서버 비용 발생
📊 대규모 프로젝트에 적합
👴 오래된 만큼 레퍼런스 풍부
GitHub Actions:
☁️ GitHub 서버에서 실행 (서버 불필요)
📝 YAML로 간단하게 설정
🏪 Marketplace에서 Action 가져다 쓰기
💸 무료 사용량 넉넉
🚀 소규모~중규모 프로젝트에 적합
🆕 상대적으로 새롭지만 빠르게 성장 중
🐙 GitHub Actions 핵심 개념
아키텍처 이해
GitHub Actions 구조:
📂 Workflow (워크플로우)
│ - 자동화된 전체 프로세스
│ - .github/workflows/*.yml 파일로 정의
│ - 이벤트에 의해 트리거됨
│
├── 🔲 Job 1 (빌드)
│ │ - 하나의 Runner(실행 환경)에서 실행
│ │ - 여러 Step으로 구성
│ │
│ ├── 📌 Step 1: 코드 체크아웃
│ ├── 📌 Step 2: JDK 설치
│ ├── 📌 Step 3: Gradle 빌드
│ └── 📌 Step 4: 테스트 실행
│
├── 🔲 Job 2 (Docker)
│ │ - Job 1 완료 후 실행 (needs: build)
│ │
│ ├── 📌 Step 1: Docker 로그인
│ ├── 📌 Step 2: 이미지 빌드
│ └── 📌 Step 3: 이미지 푸시
│
└── 🔲 Job 3 (배포)
│ - Job 2 완료 후 실행 (needs: docker)
│
├── 📌 Step 1: EC2 SSH 접속
├── 📌 Step 2: 이미지 Pull
└── 📌 Step 3: 컨테이너 재시작
핵심 용어 정리
용어 설명 비유
| Workflow | 자동화 전체 프로세스 | 🏭 공장 전체 |
| Event | Workflow를 시작시키는 트리거 | 🔔 출근 알림 |
| Job | 같은 Runner에서 실행되는 단위 | 🏭 공장의 각 라인 |
| Step | Job 내의 개별 작업 | 🔧 라인의 각 공정 |
| Action | 재사용 가능한 작업 단위 | 📦 미리 만든 도구 |
| Runner | Workflow가 실행되는 서버 | 🖥️ 작업 컴퓨터 |
| Artifact | Job 간 공유하는 파일 | 📋 작업 결과물 |
Event (트리거) 종류
# 1️⃣ Push 이벤트
on:
push:
branches: [ main, develop ]
# 2️⃣ Pull Request 이벤트
on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened ]
# 3️⃣ 수동 실행
on:
workflow_dispatch:
inputs:
environment:
description: '배포 환경'
required: true
default: 'staging'
# 4️⃣ 스케줄 (Cron)
on:
schedule:
- cron: '0 0 * * *' # 매일 자정 (UTC)
- cron: '0 9 * * 1' # 매주 월요일 오전 9시 (UTC)
# 5️⃣ 태그 푸시
on:
push:
tags:
- 'v*' # v1.0, v2.0 등
# 6️⃣ 특정 파일 변경 시만
on:
push:
branches: [ main ]
paths:
- 'src/**'
- 'build.gradle'
paths-ignore:
- '*.md'
- 'docs/**'
# 7️⃣ 다른 Workflow 완료 후
on:
workflow_run:
workflows: ["Build"]
types: [ completed ]
Runner (실행 환경)
# GitHub이 제공하는 Runner (무료!)
runs-on: ubuntu-latest # Ubuntu 22.04 (가장 많이 사용)
runs-on: ubuntu-22.04 # 특정 버전
runs-on: windows-latest # Windows Server 2022
runs-on: macos-latest # macOS 13
# Self-hosted Runner (자체 서버)
runs-on: self-hosted # 직접 관리하는 서버에서 실행
Runner 스펙 (GitHub-hosted):
┌──────────────────────────────────────┐
│ Ubuntu Runner (Standard) │
│ │
│ CPU: 2코어 │
│ RAM: 7GB │
│ Storage: 14GB SSD │
│ 사전 설치: Docker, Java, Node, etc. │
│ 네트워크: 빠른 인터넷 연결 │
└──────────────────────────────────────┘
무료 사용량:
┌──────────────────────────────────────┐
│ Public repo: 무제한 ✅ │
│ Private repo: 2,000분/월 (Linux) │
│ 1,000분/월 (Windows) │
│ 200분/월 (macOS) │
└──────────────────────────────────────┘
📝 YAML 문법 기초

GitHub Actions는 YAML 파일로 설정하기 때문에, YAML 문법을 알아야 해요.
# 1️⃣ 주석
# 이것은 주석입니다
# 2️⃣ 키-값 쌍
name: My Workflow
version: 1.0
# 3️⃣ 문자열 (따옴표 선택적)
title: Hello World
title: "Hello World"
title: 'Hello World'
# 4️⃣ 숫자
count: 42
price: 19.99
# 5️⃣ 불리언
enabled: true
debug: false
# 6️⃣ 리스트 (배열)
fruits:
- apple
- banana
- cherry
# 또는 인라인
fruits: [apple, banana, cherry]
# 7️⃣ 중첩된 객체
person:
name: Kim
age: 25
address:
city: Seoul
zip: "12345"
# 8️⃣ 멀티라인 문자열
# | : 줄바꿈 유지
description: |
This is line 1
This is line 2
# > : 줄바꿈을 공백으로
description: >
This is a very
long sentence.
# 결과: "This is a very long sentence."
# 9️⃣ 환경 변수 참조
env:
DB_HOST: ${{ secrets.DB_HOST }}
APP_ENV: production
⚠️ YAML 주의사항:
# 들여쓰기는 반드시 "스페이스"! (탭 사용 불가!)
# 2칸 들여쓰기 권장
# ❌ 탭 사용 (에러!)
jobs:
build: # 탭!
steps: # 탭!
# ✅ 스페이스 사용
jobs:
build: # 스페이스 2칸
steps: # 스페이스 4칸
# 콜론 뒤에 반드시 공백!
# ❌
name:value
# ✅
name: value
🚀 GitHub Actions Workflow 작성
프로젝트 구조
my-spring-project/
├── .github/
│ └── workflows/
│ ├── ci.yml # CI 파이프라인
│ ├── cd.yml # CD 파이프라인
│ └── ci-cd.yml # CI/CD 통합 (이걸 만들 거예요!)
├── src/
│ ├── main/
│ └── test/
├── Dockerfile
├── docker-compose.yml
├── build.gradle
└── settings.gradle
Step 1: 기본 CI Workflow (빌드 & 테스트)
# .github/workflows/ci.yml
name: CI - Build & Test
# 트리거: main, develop 브랜치에 push 또는 PR 시
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
# Ubuntu 최신 버전에서 실행
runs-on: ubuntu-latest
steps:
# 1단계: 소스 코드 체크아웃
- name: 📥 Checkout source code
uses: actions/checkout@v4
# 2단계: JDK 17 설치
- name: ☕ Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin' # Eclipse Temurin (구 AdoptOpenJDK)
# 3단계: Gradle 캐싱 (빌드 속도 향상)
- name: 📦 Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# 4단계: Gradle 실행 권한 부여
- name: 🔑 Grant execute permission for gradlew
run: chmod +x gradlew
# 5단계: 빌드
- name: 🔨 Build with Gradle
run: ./gradlew clean build -x test
# 6단계: 테스트
- name: 🧪 Run tests
run: ./gradlew test
# 7단계: 테스트 결과 리포트 업로드
- name: 📊 Upload test results
if: always() # 테스트 실패해도 리포트는 업로드
uses: actions/upload-artifact@v4
with:
name: test-results
path: build/reports/tests/test/
retention-days: 7
# 8단계: 빌드 결과물 업로드
- name: 📦 Upload build artifact
uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/*.jar
retention-days: 1
실행 결과:

✅ CI - Build & Test
✅ build (2m 34s)
✅ 📥 Checkout source code (2s)
✅ ☕ Set up JDK 17 (8s)
✅ 📦 Cache Gradle packages (1s) - Cache hit!
✅ 🔑 Grant execute permission (0s)
✅ 🔨 Build with Gradle (45s)
✅ 🧪 Run tests (38s)
✅ 📊 Upload test results (2s)
✅ 📦 Upload build artifact (3s)
Step 2: Docker 이미지 빌드 & 푸시 추가
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
# 환경 변수 정의
env:
DOCKER_IMAGE: your-dockerhub-username/my-spring-app
DOCKER_TAG: ${{ github.sha }}
jobs:
# ===== Job 1: 빌드 & 테스트 =====
build:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: ☕ Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: 📦 Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: ${{ runner.os }}-gradle-
- name: 🔑 Grant execute permission
run: chmod +x gradlew
- name: 🔨 Build
run: ./gradlew clean build -x test
- name: 🧪 Test
run: ./gradlew test
- name: 📦 Upload JAR
uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/*.jar
# ===== Job 2: Docker 이미지 빌드 & 푸시 =====
docker:
needs: build # build Job 완료 후 실행
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: 📦 Download JAR
uses: actions/download-artifact@v4
with:
name: app-jar
path: build/libs/
- name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🔑 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🏗️ Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: 🔍 Verify image
run: |
echo "✅ Image pushed: ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}"
echo "✅ Image pushed: ${{ env.DOCKER_IMAGE }}:latest"
Step 3: EC2 자동 배포 추가
# ===== Job 3: EC2 배포 =====
deploy:
needs: docker # docker Job 완료 후 실행
runs-on: ubuntu-latest
steps:
- name: 🚀 Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
echo "🔄 Starting deployment..."
# Docker Hub에서 최신 이미지 pull
docker pull ${{ env.DOCKER_IMAGE }}:latest
# 기존 컨테이너 중지 및 삭제
docker stop spring-app || true
docker rm spring-app || true
# 새 컨테이너 실행
docker run -d \
--name spring-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
--restart unless-stopped \
${{ env.DOCKER_IMAGE }}:latest
# 헬스 체크 (최대 60초 대기)
echo "⏳ Waiting for application to start..."
for i in {1..12}; do
if curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; then
echo "✅ Application is healthy!"
exit 0
fi
echo "Waiting... ($i/12)"
sleep 5
done
echo "❌ Health check failed!"
docker logs spring-app --tail 50
exit 1
- name: ✅ Deployment complete
run: echo "🎉 Successfully deployed to EC2!"
완성된 CI/CD Workflow 전체

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
env:
DOCKER_IMAGE: your-dockerhub-username/my-spring-app
DOCKER_TAG: ${{ github.sha }}
jobs:
# ===== Job 1: 빌드 & 테스트 =====
build:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: ☕ Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: 📦 Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: ${{ runner.os }}-gradle-
- name: 🔑 Grant execute permission
run: chmod +x gradlew
- name: 🔨 Build
run: ./gradlew clean build -x test
- name: 🧪 Test
run: ./gradlew test
- name: 📦 Upload JAR
uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/*.jar
# ===== Job 2: Docker 이미지 빌드 & 푸시 =====
docker:
needs: build
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: 📦 Download JAR
uses: actions/download-artifact@v4
with:
name: app-jar
path: build/libs/
- name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🔑 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🏗️ Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
# ===== Job 3: EC2 배포 =====
deploy:
needs: docker
runs-on: ubuntu-latest
steps:
- name: 🚀 Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
envs: DOCKER_IMAGE
script: |
docker pull ${{ env.DOCKER_IMAGE }}:latest
docker stop spring-app || true
docker rm spring-app || true
docker run -d \
--name spring-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
--restart unless-stopped \
${{ env.DOCKER_IMAGE }}:latest
for i in {1..12}; do
if curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; then
echo "✅ Healthy!"
exit 0
fi
sleep 5
done
echo "❌ Health check failed!"
exit 1
🔐 Secrets 관리
GitHub Secrets 설정 방법

절대 코드에 비밀번호, API 키 같은 걸 직접 쓰면 안 돼요!
# ❌ 절대 이렇게 하면 안 됨!!
- name: Login to Docker Hub
run: docker login -u myuser -p mypassword123
# ✅ Secrets 사용
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Secrets 등록 방법:
1. GitHub 저장소 → Settings
2. 왼쪽 메뉴 → Secrets and variables → Actions
3. "New repository secret" 클릭
4. Name과 Value 입력 후 저장
등록할 Secrets 목록:
┌────────────────────────┬──────────────────────────────────┐
│ Secret Name │ 설명 │
├────────────────────────┼──────────────────────────────────┤
│ DOCKERHUB_USERNAME │ Docker Hub 아이디 │
│ DOCKERHUB_TOKEN │ Docker Hub Access Token │
│ EC2_HOST │ EC2 퍼블릭 IP (예: 3.xx.xx.xx) │
│ EC2_USERNAME │ EC2 접속 사용자명 (보통 ubuntu) │
│ EC2_SSH_KEY │ EC2 SSH 프라이빗 키 (.pem 내용) │
│ DB_PASSWORD │ 데이터베이스 비밀번호 │
└────────────────────────┴──────────────────────────────────┘
Docker Hub Access Token 발급:
1. Docker Hub (https://hub.docker.com) 로그인
2. 우측 상단 프로필 → Account Settings
3. Security → New Access Token
4. Token description: "GitHub Actions"
5. Access permissions: Read & Write
6. Generate 클릭
7. 발급된 토큰 복사 → GitHub Secrets에 저장!
⚠️ 토큰은 한 번만 보여주므로 바로 복사해야 해요!
EC2 SSH Key 등록:
# .pem 파일 내용 확인
cat my-key.pem
# 출력 내용 전체를 복사해서 GitHub Secrets에 등록
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
(중간 내용 전부)
...
-----END RSA PRIVATE KEY-----
# 위 내용 전체를 EC2_SSH_KEY Secret에 저장
Environment 활용

# 환경별 설정 분리
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # staging 환경의 Secrets 사용
steps:
- name: Deploy to staging
run: echo "Deploying to ${{ vars.SERVER_URL }}"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://my-app.com
steps:
- name: Deploy to production
run: echo "Deploying to production"
Environment 기능:
✅ 환경별 Secrets 분리 (staging vs production)
✅ 보호 규칙 설정 (승인 필요 등)
✅ 배포 이력 추적
✅ 특정 브랜치만 배포 허용
설정: Repository → Settings → Environments
🌿 브랜치 전략과 CI/CD 연동
Git Flow + CI/CD

브랜치 전략:
main (프로덕션)
│
├── develop (개발)
│ │
│ ├── feature/login
│ │ → PR to develop → CI (빌드 + 테스트)
│ │
│ ├── feature/payment
│ │ → PR to develop → CI (빌드 + 테스트)
│ │
│ └── PR to main → CI + CD (빌드 + 테스트 + 배포)
│
└── hotfix/urgent-fix
→ PR to main → CI + CD (긴급 배포)
브랜치별 Workflow 설정:
# .github/workflows/ci.yml - PR 시 빌드/테스트만
name: CI
on:
pull_request:
branches: [ main, develop ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- run: chmod +x gradlew
- run: ./gradlew clean build test
# .github/workflows/cd.yml - main 머지 시 배포
name: CD
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# ... 빌드 → Docker → 배포
PR에 빌드 상태 표시
Pull Request 화면:
┌─────────────────────────────────────────────────┐
│ feature/login → develop │
│ │
│ ✅ CI - Build & Test │
│ All checks have passed │
│ │
│ [Merge pull request] 버튼 활성화! │
└─────────────────────────────────────────────────┘
만약 테스트 실패 시:
┌─────────────────────────────────────────────────┐
│ feature/login → develop │
│ │
│ ❌ CI - Build & Test │
│ 1 check failed │
│ │
│ [Merge pull request] 버튼 비활성화! │
│ → 테스트 통과할 때까지 머지 불가 │
└─────────────────────────────────────────────────┘
Branch Protection Rule 설정:
Repository → Settings → Branches → Branch protection rules
main 브랜치 보호 규칙:
✅ Require a pull request before merging
✅ Require status checks to pass before merging
→ "CI - Build & Test" 선택
✅ Require branches to be up to date before merging
→ CI가 통과하지 않으면 main에 머지 불가능! 안전!
📱 Slack / Discord 알림 연동
배포 결과 알림 받기

# Job 3에 알림 Step 추가
deploy:
needs: docker
runs-on: ubuntu-latest
steps:
- name: 🚀 Deploy to EC2
# ... 배포 로직 ...
# 성공 알림
- name: ✅ Notify success
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: |
🎉 배포 성공!
브랜치: ${{ github.ref_name }}
커밋: ${{ github.event.head_commit.message }}
작성자: ${{ github.actor }}
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
# 실패 알림
- name: ❌ Notify failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: |
🚨 배포 실패!
브랜치: ${{ github.ref_name }}
커밋: ${{ github.event.head_commit.message }}
작성자: ${{ github.actor }}
확인: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
Discord 알림:
- name: 📢 Discord notification
if: always()
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
title: "배포 결과"
description: |
**상태:** ${{ job.status }}
**브랜치:** ${{ github.ref_name }}
**커밋:** ${{ github.event.head_commit.message }}
🔥 실전 응용 Workflow
Docker Compose를 사용한 배포

deploy:
needs: docker
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout (docker-compose.yml 필요)
uses: actions/checkout@v4
- name: 📤 Copy docker-compose to EC2
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
source: "docker-compose.yml"
target: "/home/ubuntu/app"
- name: 🚀 Deploy with Docker Compose
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/app
# 환경 변수 파일 생성
cat > .env << EOF
DOCKER_IMAGE=${{ env.DOCKER_IMAGE }}
DOCKER_TAG=latest
DB_PASSWORD=${{ secrets.DB_PASSWORD }}
REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}
EOF
# 최신 이미지 pull
docker compose pull
# 서비스 재시작 (무중단)
docker compose up -d --remove-orphans
# 미사용 이미지 정리
docker image prune -f
# 상태 확인
docker compose ps
테스트 리포트 자동 생성
build:
runs-on: ubuntu-latest
steps:
# ... 빌드 & 테스트 ...
- name: 📊 Publish test results
if: always()
uses: dorny/test-reporter@v1
with:
name: JUnit Test Results
path: build/test-results/test/*.xml
reporter: java-junit
fail-on-error: false
- name: 📈 JaCoCo code coverage
if: always()
uses: madrapps/jacoco-report@v1.6
with:
paths: build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 60
min-coverage-changed-files: 80
멀티 환경 배포 (Staging → Production)
name: Multi-Environment Deploy
on:
push:
branches: [ main ]
jobs:
build:
# ... 빌드 & 테스트 ...
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: 🟡 Deploy to Staging
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_IMAGE }}:latest
docker stop spring-app || true && docker rm spring-app || true
docker run -d --name spring-app -p 8080:8080 ${{ env.DOCKER_IMAGE }}:latest
echo "✅ Staging deployment complete"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
# ↓ 수동 승인 필요 (Environment 설정에서)
steps:
- name: 🟢 Deploy to Production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_IMAGE }}:latest
docker stop spring-app || true && docker rm spring-app || true
docker run -d --name spring-app -p 8080:8080 ${{ env.DOCKER_IMAGE }}:latest
echo "✅ Production deployment complete"
🛠️ 실전 트러블슈팅
문제 1: Gradle 빌드 실패 - 권한 문제
Error: ./gradlew: Permission denied
원인: gradlew 파일에 실행 권한이 없음
해결:
# Workflow에 권한 부여 Step 추가
- name: 🔑 Grant execute permission
run: chmod +x gradlew
또는 로컬에서 미리 권한 설정:
# 로컬에서 실행 권한 부여 후 커밋
git update-index --chmod=+x gradlew
git commit -m "fix: gradlew 실행 권한 추가"
git push
문제 2: Docker Hub 로그인 실패
Error: Username and password required
원인: Secrets 설정 누락 또는 잘못된 값
해결:
1. GitHub Secrets 확인
Repository → Settings → Secrets → Actions
2. Secret 이름이 정확한지 확인:
DOCKERHUB_USERNAME (오타 주의!)
DOCKERHUB_TOKEN (비밀번호가 아닌 Access Token!)
3. Docker Hub Access Token 재발급:
Docker Hub → Account Settings → Security → New Access Token
문제 3: EC2 SSH 접속 실패
ssh: connect to host xx.xx.xx.xx port 22: Connection timed out
원인들 & 해결:
1️⃣ EC2 보안 그룹에서 22번 포트 미오픈
→ EC2 → Security Groups → Inbound Rules
→ SSH (22) 포트 추가
2️⃣ EC2 퍼블릭 IP가 변경됨 (재시작 시)
→ Elastic IP를 할당해서 고정!
→ 또는 Secrets의 EC2_HOST 업데이트
3️⃣ SSH 키 형식 문제
→ Secrets에 .pem 내용 등록 시
-----BEGIN RSA PRIVATE KEY----- 부터
-----END RSA PRIVATE KEY----- 까지 전체 복사!
앞뒤 공백이나 줄바꿈 주의!
문제 4: Gradle 캐시가 안 먹힘
Cache not found for input keys: Linux-gradle-abc123...
원인: 캐시 키가 매번 변경됨
해결:
# 캐시 키를 build.gradle의 해시로 설정
- name: 📦 Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# hashFiles: build.gradle이 변경될 때만 새 캐시 생성
# restore-keys: 정확한 키가 없으면 부분 일치로 복원
문제 5: 테스트 시 DB 연결 실패
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'dataSource'
원인: CI 환경에는 MySQL이 없음
해결 방법 1: H2 인메모리 DB 사용 (추천)
# application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
# Workflow에서 test 프로파일 사용
- name: 🧪 Test
run: ./gradlew test -Dspring.profiles.active=test
해결 방법 2: GitHub Actions에서 MySQL 서비스 실행
jobs:
build:
runs-on: ubuntu-latest
# 서비스 컨테이너 (MySQL)
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root1234
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- name: 🧪 Test with MySQL
env:
SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/testdb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root1234
run: ./gradlew test
문제 6: Workflow가 너무 오래 걸림
빌드: 3분 + Docker: 4분 + 배포: 2분 = 총 9분 😢
최적화 방법:
# 1️⃣ Gradle 캐싱 (필수!)
- uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
# 2️⃣ Docker 레이어 캐싱
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
# 3️⃣ 불필요한 파일 제외
# .github/.dockerignore 또는 .dockerignore 활용
# 4️⃣ 테스트 병렬 실행
- name: 🧪 Test (parallel)
run: ./gradlew test --parallel
# 5️⃣ 변경된 파일만 빌드 트리거
on:
push:
paths:
- 'src/**'
- 'build.gradle'
paths-ignore:
- '*.md'
- 'docs/**'
최적화 결과:
Before: 9분 → After: 4분 ⚡
(캐시 적중 시)
🧩 유용한 GitHub Actions 마켓플레이스

GitHub Marketplace · Actions to improve your workflow · GitHub
자주 사용하는 Actions:
📦 기본
├── actions/checkout@v4 - 코드 체크아웃
├── actions/setup-java@v4 - Java 설치
├── actions/cache@v4 - 캐싱
├── actions/upload-artifact@v4 - 산출물 업로드
└── actions/download-artifact@v4 - 산출물 다운로드
🐳 Docker
├── docker/setup-buildx-action@v3 - Docker Buildx
├── docker/login-action@v3 - Docker Hub 로그인
└── docker/build-push-action@v5 - 빌드 & 푸시
🚀 배포
├── appleboy/ssh-action@v1 - SSH로 서버 접속
├── appleboy/scp-action@v0.1.7 - 파일 전송 (SCP)
└── aws-actions/configure-aws-credentials@v4 - AWS 인증
📊 품질
├── dorny/test-reporter@v1 - 테스트 리포트
├── madrapps/jacoco-report@v1.6 - 코드 커버리지
└── github/codeql-action@v3 - 보안 스캔
📢 알림
├── 8398a7/action-slack@v3 - Slack 알림
└── sarisia/actions-status-discord@v1 - Discord 알림
💡 오늘 나는 무엇을 배웠는가
1. "자동화할 수 있으면 자동화하라"
수동 반복 작업:
빌드 → 테스트 → Docker → Push → SSH → Pull → Run
자동화 후:
git push
→ 사람은 창의적인 일에 집중하고,
반복적인 일은 기계에게!
2. CI/CD는 "안전장치"다
CI가 없을 때:
"테스트? 귀찮은데 그냥 배포하자..."
→ 🔥 버그가 운영 서버에!
CI가 있을 때:
"테스트 실패!" → 머지 차단
→ ✅ 버그 있는 코드는 절대 배포 불가!
CI/CD = 실수를 시스템이 잡아주는 안전망
3. Secrets 관리의 중요성
❌ 코드에 비밀번호 하드코딩
→ Git 히스토리에 영원히 남음
→ 누군가 fork하면 비밀번호 유출!
✅ GitHub Secrets 사용
→ 암호화되어 저장
→ 로그에도 마스킹 처리 (****)
→ 권한 있는 사람만 접근
4. "작게, 자주, 빠르게" 배포하자
옛날: 한 달에 한 번 대규모 배포
→ 문제 발생 시 원인 찾기 어려움
→ 롤백 범위가 큼
→ 스트레스 😰
CI/CD: 하루에도 여러 번 소규모 배포
→ 문제가 작고 원인 파악 쉬움
→ 롤백이 간단
→ 자신감 있는 배포 💪
😊 좋았던 점 & 😅 아쉬웠던 점
좋았던 점
👍 배포 스트레스 해소
예전에는 배포할 때마다 "잘 되겠지...?"라는 불안감이 있었는데, CI/CD를 구축하고 나니 push만 하면 자동으로 검증되니까 마음이 편해졌어요.
👍 팀 프로젝트에 바로 적용 가능
GitHub를 이미 사용하고 있어서, .github/workflows/ 폴더에 YAML 파일 하나만 추가하면 바로 CI/CD가 동작해요. 별도 서버 없이!
👍 포트폴리오 차별화
CI/CD 파이프라인이 있는 프로젝트는 확실히 면접에서 이야기할 거리가 많아요.
"이 프로젝트는 main 브랜치에 push하면 자동으로 테스트 → Docker 빌드 → EC2 배포까지 됩니다." → 면접관: 👀✨
👍 Docker와 시너지
지난번에 배운 Docker와 완벽하게 연결돼서, Docker로 컨테이너화 → GitHub Actions로 자동 배포라는 하나의 흐름이 완성됐어요!
아쉬웠던 점
😢 무중단 배포까지는 못 함
현재 방식은 기존 컨테이너를 중지하고 새 컨테이너를 띄우는 방식이라, 몇 초간 서비스 다운타임이 있어요. Blue-Green이나 Rolling 배포를 적용하면 해결할 수 있다고 하는데 다음에 도전!
😢 모니터링 부재
배포는 자동화했는데, "배포 후 서비스가 잘 돌아가고 있는지"를 확인하는 모니터링은 아직 부족해요.
😢 테스트 커버리지가 낮음
CI에서 테스트를 자동으로 돌려도, 테스트 코드 자체가 부족하면 의미가 없더라고요. 테스트 코드 작성도 함께 신경 써야겠어요.
🤔 어려웠던 점과 해결
문제 1: YAML 들여쓰기 지옥
처음에 YAML 문법이 익숙하지 않아서 들여쓰기 실수로 계속 Workflow가 깨졌어요.
# ❌ 들여쓰기 실수 (탭을 써버림)
jobs:
build:
steps: # 탭! → 에러!
해결: VS Code에 YAML 확장 프로그램 설치하고, 탭을 스페이스 2칸으로 자동 변환 설정!
문제 2: Secrets가 로그에 안 찍힘
디버깅하려고 Secrets 값을 로그에 찍으려 했는데 ***로 마스킹 되어서 뭐가 문제인지 알 수가 없었어요.
# 로그에 ****로 표시됨
- run: echo ${{ secrets.DOCKERHUB_USERNAME }}
# 출력: ***
해결: Secrets가 아닌 일반적인 환경 변수는 출력해서 확인하고, Secrets 자체가 맞는지는 로컬에서 먼저 테스트!
문제 3: Job 간 파일 공유
Build Job에서 만든 JAR 파일이 Docker Job에서 안 보였어요.
원인: 각 Job은 별도의 Runner에서 실행되므로 파일 시스템을 공유하지 않음!
해결: Artifact로 파일을 업로드/다운로드:
# Job 1: 업로드
- uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/*.jar
# Job 2: 다운로드
- uses: actions/download-artifact@v4
with:
name: app-jar
path: build/libs/
🎓 나만의 학습 팁
1. 작은 Workflow부터 시작
# 처음부터 복잡하게 하지 말고, 이것부터!
name: Hello CI
on: push
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, CI/CD! 🎉"
이게 돌아가는 걸 확인한 다음에 하나씩 추가하면 돼요!
2. act로 로컬 테스트
# act 설치 (GitHub Actions 로컬 실행 도구)
# Mac
brew install act
# 로컬에서 Workflow 실행
act push
# 특정 Job만 실행
act -j build
# push 할 때마다 GitHub에서 확인하지 않아도 됨!
3. Workflow 실행 디버깅
# 디버그 로깅 활성화
# Repository → Settings → Secrets → Actions
# ACTIONS_RUNNER_DEBUG = true
# ACTIONS_STEP_DEBUG = true
# Step에서 직접 디버그 정보 출력
- name: Debug info
run: |
echo "Event: ${{ github.event_name }}"
echo "Branch: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Runner OS: ${{ runner.os }}"
echo "Workspace: ${{ github.workspace }}"
ls -la
4. README에 배지 추가
Static Badge | Shields.io
The color of the logo (hex, rgb, rgba, hsl, hsla and css named colors supported). Supported for simple-icons logos but not for custom logos.
shields.io
Ileriayo/markdown-badges: Badges for your personal developer branding, profile, and projects.
GitHub - Ileriayo/markdown-badges: Badges for your personal developer branding, profile, and projects.
Badges for your personal developer branding, profile, and projects. - Ileriayo/markdown-badges
github.com
Simple Icons
simpleicons.org


README에 이 배지가 있으면 프로젝트가 전문적으로 보여요! ✨
5. 커밋 메시지로 Workflow 건너뛰기
# CI를 건너뛰고 싶을 때 (문서만 수정한 경우 등)
git commit -m "docs: README 수정 [skip ci]"
# 또는
git commit -m "chore: 주석 수정 [no ci]"
🚀 다음 학습 목표
📌 단기 목표 (이번 주)
✓ 팀 프로젝트에 CI/CD 파이프라인 적용
✓ PR 시 자동 빌드 & 테스트 확인
✓ main 브랜치 머지 시 자동 배포
📌 중기 목표 (이번 달)
✓ 무중단 배포 구현 (Blue-Green or Rolling)
✓ 테스트 커버리지 리포트 자동화
✓ 모니터링 연동 (배포 후 헬스 체크 강화)
📌 장기 목표 (부트캠프 기간)
✓ Kubernetes 기반 배포로 전환
✓ ArgoCD를 활용한 GitOps
✓ 전체 인프라를 코드로 관리 (Terraform)
🎬 마치며
CI/CD를 배우기 전에는 배포가 늘 부담스러운 작업이었어요. "혹시 뭐가 잘못되면 어쩌지?", "빌드 깜빡하면 어쩌지?" 같은 걱정을 항상 했었거든요.
하지만 GitHub Actions로 CI/CD를 구축하고 나니 이런 생각이 들었어요:
"아, 이제 push만 하면 되는구나!"
코드를 수정하고, push하고, 커피 한 잔 마시고 오면 이미 배포가 끝나있어요. ☕
특히 Docker와 조합했을 때의 시너지가 대단해요:
Docker: "어디서든 동일하게 실행"
CI/CD: "push만 하면 알아서 배포"
Docker + CI/CD = "코드만 작성하면 나머지는 자동!" 🚀
아직 무중단 배포나 Kubernetes 같은 심화 주제가 남아있지만, 지금까지 배운 것만으로도 꽤 그럴듯한 배포 파이프라인이 완성됐어요.
다음에 배울 주제가 기대됩니다! 이 시리즈를 읽어주시는 분들, 오늘도 화이팅이에요! 💪😊
📚 참고 자료
공식 문서
추천 블로그
추천 도서
- "DevOps와 SE를 위한 리눅스 커널 이야기" - 강진우
- "그림으로 공부하는 IT 인프라 구조" - 야마자키 야스시 외
GitHub Actions 템플릿
- starter-workflows - GitHub 공식 템플릿