FireDrago

[GithubActions] GithubActions로 Docker CI 구성하기 본문

프로그래밍/배포

[GithubActions] GithubActions로 Docker CI 구성하기

화이용 2025. 7. 27. 16:55

CI (Continuous Integration) : 지속적인 통합

 
 

CI 과정은 여러 개발자가 작업한 코드를 중앙 저장소에 정기적으로 통합하는 과정을 말한다.

새로운 코드가 추가될 때마다 자동으로 테스트 빌드를 거쳐, 기존 코드와 충돌 없이 잘 통합되는지 검증한다.

CI와 Docker

Docker를 사용하면 CI 과정을 어디서나 동일한 환경에서 수행할 수 있다. (개발자 컴퓨터, 개발 서버, 운영 서버 등등)

코드 테스트와 빌드 과정이 끝나면 최종 산출물과 실행 환경등을 담은 도커 이미지로 만든다.

GithubActions

CI/CD를 위한 대표적인 도구로 Jenkins GithubActions가 있다.

Actions는 jenkins에 비해 CI/CD구성이 간편하다.

프로젝트 .github/workflow 에 yml 파일로 작업을 정의해 줄 수 있다.

반면 Jenkins는 직접 서버에 설치해야 하고 학습 곡선이 더 높은 대신, Actions 보다 다양한 작업을 가능하게 해준다.

(여러 서버를 한번에 관리 해야 한다거나, 애플리케이션 서버 private Ip 설정등)

현재 프로젝트 단계에서는 쉽게 적용할 수 있는 GithubActions를 도입하기로 팀원들과 합의했다.

 

코드로 보기

name: Dev Server CI

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [develop]
    paths: ['server/**']

jobs:
  # 작업을 하나로 통합하여 테스트, 빌드, 이미지 생성을 순차적으로 진행
  build-and-push:
    name: Test, Build and Push Docker Image
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      # 1. 레포지토리 코드 체크아웃
      - name: Checkout
        uses: actions/checkout@v4

      # 2. JDK 21 (Temurin) 환경 설정
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      # 3. Gradle 캐시 설정 (빌드 속도 향상)
      - name: Cache Gradle dependencies
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('server/**/*.gradle*', 'server/**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      # 4. ✨ 중요: Gradle을 사용해 테스트와 .jar 파일 빌드를 한 번에 실행
      - name: Build with Gradle
        run: ./gradlew build
        working-directory: ./server

      # 5. Docker Buildx 환경 설정
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # 6. Docker Hub 로그인
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # 7. ✨ 중요: Docker 이미지를 빌드하고 푸시 (단순화된 Dockerfile 사용)
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./server 
          file: ./server/Dockerfile
          push: true
          platforms: linux/arm64,linux/amd64
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/moment:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/moment:${{ github.sha }}
          cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache
          cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache,mode=max

위 파일은 yml 파일을 통해 ci 과정을 정의해놓은 것이다. 

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [develop]
    paths: ['server/**']

먼저 트리거 (발동 조건)을 설정해준다.

pr 제출, 커밋 푸쉬, 다시 열린 경우에 실행된다.

paths: 설정을 통해 소스코드의 server 디렉토리 이하의 변경에 해당될때만 작동하도록 설정했다.

build-and-push:
    name: Test, Build and Push Docker Image
    runs-on: ubuntu-latest
    permissions:
      contents: read

GithubActions는 해당 작업을 실행할때 새로운 가상 공간을 만든다.

runs-on: ubuntu-latest  가상공간의 환경을 우분투 최신 버전으로 설정한다.

permissions: contents: read   해당 가상 공간이 소스코드를 읽을 수 있는 권한을 부여한다.

 

소스코드를 테스트 하고 .jar 파일로 변환하기 위해 jdk21 설치하고 gradle을 통해 빌드를 실행한다. 
이 과정은 이해하기 어렵지 않다.

# 7. ✨ 중요: Docker 이미지를 빌드하고 푸시 (단순화된 Dockerfile 사용)
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./server 
          file: ./server/Dockerfile
          push: true
          platforms: linux/arm64,linux/amd64
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/moment:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/moment:${{ github.sha }}
          cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache
          cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache,mode=max

빌드된 jar 파일을 DockerHub에 push 하는 과정이다.

file: ./server/Dockerfile 경로를 통해 Dockerfile을 읽고 설정대로 이미지를 생성한다.

context: ./server 를 설정해주어서 Dockerfile에서 ./server 내부에서 작업을 진행 할 수 있게된다.

Dockerfile을 살펴보자

# 1. 실행에 필요한 최소한의 JRE(Java Runtime Environment) 이미지를 사용
FROM eclipse-temurin:21-jre-jammy

# 2. 컨테이너 내부의 작업 디렉토리 설정
WORKDIR /app

# 3. CI에서 이미 빌드된 .jar 파일을 컨테이너 안으로 복사
COPY build/libs/*.jar app.jar

# 4. 컨테이너 시작 시 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]

GithubActions는 이 Dockerfile대로 이미지를 생성한다. 

 

CI 과정에서는 소스 코드를 컴파일하기 위해 javac를 포함한 JDK가 필요하지만,
최종 이미지에는 이미 빌드된 .jar 파일을 실행하기 위한 JRE만 포함시켜 이미지 크기를 최소화하고 보안을 강화한다.

 

COPY 명령어에서 앞서 CI 과정에서 context 명령어에 정의한 ./server 디렉토리 내부에서 jar 파일을 찾는다.

 

빌드된 이미지가 DockerHub에 업로드 되면 CI 과정이 마무리된다.