Nick Dev

[SPURT] Docker와 GitHub Actions를 활용한 CI/CD 구축 가이드 본문

SPURT

[SPURT] Docker와 GitHub Actions를 활용한 CI/CD 구축 가이드

Nick99 2025. 3. 7. 18:28
반응형

프로젝트 github

CICD 흐름

CI 흐름

  1. develop 브랜치에 push 일어나면 아래 flow가 실행
  2. gradle로 spring boot 프로젝트 build
  3. docker image 생성 후, docker hub에 업로드

CD 흐름

  1. 클라우드 서버에 ssh로 접속
  2. docker hub에서 image 최신화 & github secret에 있는 환경변수 가져오기
  3. 서버에 작성해둔 docker-compose 실행 (기존 컨테이너 죽이고 실행)

DEV 환경용 CICD yaml 파일

name: "[DEV] SPURT API CI/CD"

on:
  push:
    branches: [ develop ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v3
        with:
          distribution: 'corretto'
          java-version: '17'

      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Create environment variable file from DEV_ENV secret
        run: |
          echo "${{ secrets.DEV_ENV }}" > .env
          echo "Environment variable file (.env) created:"

      - name: Load environment variables from .env file
        run: |
          if [ -f .env ]; then
            while IFS= read -r line; do
              if [[ $line != \#* ]] && [[ $line == *"="* ]]; then
                echo "$line" >> $GITHUB_ENV
              fi
            done < .env
            echo "Environment variables loaded from .env"
          else
            echo ".env file not found!"
          fi

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test -Dfile.encoding=UTF-8

      - name: Docker Login
        run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker image
        run: |
          docker build -f Dockerfile-dev -t ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest .

      - name: Push Docker image to Docker Hub
        run: |
          docker push ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Server
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ secrets.DEV_HOST }}
          username: ${{ secrets.DEV_USER }}
          key: ${{ secrets.DEV_SSH_KEY }}
          script: |
            cd ${{ secrets.DEV_COMPOSE_PATH }}

            echo "${{ secrets.DEV_ENV }}" > .env
            cat .env || echo ".env file is missing!"

            docker pull ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest

            if [ "$(docker ps -q -f name=spurt-api-dev)" ]; then
              echo "Stopping and removing existing container..."
              docker compose down
            else
              echo "No running container found."
            fi

            docker compose up -d

            echo "Checking container status..."
            docker ps -a | grep spurt-api-dev

Dockerfile

FROM openjdk:17-jdk-alpine

WORKDIR /app

# Gradle 빌드 후 생성된 jar 파일을 컨테이너로 복사
# JAR_FILE 변수에 실제 jar 파일 경로를 지정
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=dev", "-jar", "/app/app.jar"]

docker-compose.yml

서버에 정의해둘 compose 파일

version: '3.8'

services:
  spurt-api:
    image: ${DOCKER_IMAGE_NAME_DEV}
    container_name: spurt-api-dev
    restart: always
    ports:
      - "8080:8080"
    env_file:
      - .env

하나씩 뜯어보쟈

1. trigger

on:
  push:
    branches: [ develop ]
  • develop 브랜치에 push가 발생하면 이 workflow가 실행됨

2. build 환경 설명

jobs:
  build:
    runs-on: ubuntu-latest
  • build 작업이 최신 ubuntu 환경에서 실행

3. 코드 체크아웃 & 4. Java 환경 설정

- uses: actions/checkout@v3
- uses: actions/setup-java@v3
  with:
    distribution: 'corretto'
    java-version: '17'
  • 현재 repo의 전체 소스 코드를 워크 스페이스로 가져옴
    • 이렇게 가져와야 빌드 작업을 할 수 있음!
  • Java 기반 프로젝트 빌드에 필요한 환경을 설정

4. Gradle 캐싱

- name: Gradle Caching
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    restore-keys: |
      ${{ runner.os }}-gradle-
  • Gradle의 캐시(~/.gradle/caches~/.gradle/wrapper)를 저장해, 이후 빌드 시 캐시를 활용하여 빌드 속도를 개선

5. .env 파일 생성

- name: Create environment variable file from DEV_ENV secret
  run: |
    echo "${{ secrets.DEV_ENV }}" > .env
    echo "Environment variable file (.env) created:"

- name: Load environment variables from .env file
  run: |
    if [ -f .env ]; then
      while IFS= read -r line; do
        if [[ $line != \#* ]] && [[ $line == *"="* ]]; then
          echo "$line" >> $GITHUB_ENV
        fi
      done < .env
      echo "Environment variables loaded from .env"
    else
      echo ".env file not found!"
    fi
  • GitHub Secrets에 저장된 DEV_ENV 값을 가져와서 .env 파일에 저장
  • .env 파일에서 환경 변수를 읽어와 GitHub Actions 워크플로우에서 사용할 수 있도록 설정

6. Gradle Wrapper 실행 권한 부여

- name: Grant execute permission for gradlew
  run: chmod +x gradlew
  • build 명령어 수행을 위한 실행 권한 부여

7. Gradle 빌드 실행

- name: Build with Gradle
  run : ./gradlew clean build -x test -Dfile.encoding=UTF-8|
  • 프로젝트를 클린(build 전 이전 결과물 제거)하고 빌드
  • -x test 옵션으로 테스트를 건너뜀
  • 인코딩 설정(-Dfile.encoding=UTF-8)

8. Docker 로그인 & 이미지 빌드

- name: Docker Login
  run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}

- name: Build Docker image
  run: |
    docker build -f Dockerfile -t ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest .
  • Docker Hub에 로그인
  • 현재 디렉터리에 있는 Dockerfile을 사용해 Docker 이미지를 빌드
    • 빌드된 이미지는 ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest 라는 태그로 저장

9. Docker 이미지 업로드

- name: Push Docker image to Docker Hub
  run: |
    docker push ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest
  • 빌드한 Docker 이미지를 Docker Hub에 푸시

10. Deploy

deploy:
  needs: build
  runs-on: ubuntu-latest
  steps:
    - name: Deploy to Server
      uses: appleboy/ssh-action@v0.1.7
      with:
        host: ${{ secrets.DEV_HOST }}
        username: ${{ secrets.DEV_USER }}
        key: ${{ secrets.DEV_SSH_KEY }}
        script: |
          cd ${{ secrets.DEV_COMPOSE_PATH }}

          echo "${{ secrets.DEV_ENV }}" > .env
          cat .env || echo ".env file is missing!"

          docker pull ${{ secrets.DOCKER_IMAGE_NAME_DEV }}:latest

          if [ "$(docker ps -q -f name=spurt-api-dev)" ]; then
            echo "Stopping and removing existing container..."
            docker compose down
          else
            echo "No running container found."
          fi

          docker compose up -d

          echo "Checking container status..."
          docker ps -a | grep spurt-api-dev
  • deploy 작업은 build 작업이 성공적으로 완료된 후 실행
  1. appleboy/ssh-action 액션을 사용하여 SSH로 원격 서버에 접속
  2. 서버에서 docker-compose 파일있는 디렉토리로 이동
  3. github secret에 있는 DEV_ENV 파일을 서버에 .env 파일로 복사
  4. docker hub에서 docker image 가져오기
  5. 이전에 실행 중이였던 컨테이너 있으면 죽이고 새로 컨테이너 실행
반응형