Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/cd-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CD [Development]

on:
push:
branches:
- develop
paths:
- 'src/**'
- 'build.gradle.kts'
- 'docker/**'
- 'cd.yml'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

워크플로우 파일 경로가 잘못되었습니다.

Line 11에서 cd.yml을 참조하고 있지만, 실제 파일명은 cd-dev.yml입니다. 이로 인해 워크플로우 파일 변경 시 자동 배포가 트리거되지 않습니다.

🔧 제안하는 수정안
     paths:
       - 'src/**'
       - 'build.gradle.kts'
       - 'docker/**'
-      - 'cd.yml'
+      - '.github/workflows/cd-dev.yml'
🤖 Prompt for AI Agents
In @.github/workflows/cd-dev.yml around lines 7 - 11, The workflow currently
references 'cd.yml' in the paths list which is incorrect; update the paths array
in .github/workflows/cd-dev.yml to replace 'cd.yml' with the actual filename
'cd-dev.yml' so changes to the deployment workflow trigger correctly (edit the
entry that currently reads 'cd.yml' to 'cd-dev.yml').


workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
type: choice
options:
- dev
- prod

jobs:
# 1. 공통 빌드/테스트 워크플로우 호출 (ci.yml 재사용)
ci-and-build:
uses: ./.github/workflows/ci.yml
with:
environment: ${{ inputs.environment }}
secrets: inherit

# 2. Deploy Job
deploy:
needs: ci-and-build
runs-on: ubuntu-latest
# 빌드 단계에서 결정된 환경 사용
environment: ${{ needs.ci-and-build.outputs.environment }}

env:
ENVIRONMENT: ${{ needs.ci-and-build.outputs.environment }}
REPO_OWNER: ${{ needs.ci-and-build.outputs.repo_owner }}
IMAGE_TAG: ${{ needs.ci-and-build.outputs.image_tag }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: SSH 접속 및 스크립트 실행
uses: appleboy/ssh-action@v1
env:
APPLICATION_PROD: ${{ secrets.APPLICATION_PROD }}
APPLICATION_DEV: ${{ secrets.APPLICATION_DEV }}
APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
with:
host: ${{ secrets.SERVER_SSH_HOST }}
username: ${{ secrets.SERVER_SSH_USERNAME }}
key: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_SSH_PORT }}
envs: APPLICATION_PROD,APPLICATION_DEV,APPLICATION_SECRET,ENVIRONMENT,IMAGE_TAG,REPO_OWNER,DOCKER_IMAGE_NAME,DOCKERHUB_TOKEN,DOCKERHUB_USERNAME
script_path: scripts/cd-dev.sh

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

필수 환경 변수가 누락되었습니다.

Line 59의 envs 목록에 APP_DIR_PRODUCTIONAPP_DIR_DEVELOPMENT가 포함되어 있지 않습니다. 배포 스크립트(cd-dev.sh)는 이 변수들을 필수로 검증하므로(lines 16-17), 없으면 배포가 실패합니다.

🐛 제안하는 수정안
       - name: SSH 접속 및 스크립트 실행
         uses: appleboy/ssh-action@v1
         env:
           APPLICATION_PROD: ${{ secrets.APPLICATION_PROD }}
           APPLICATION_DEV: ${{ secrets.APPLICATION_DEV }}
           APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET }}
           DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
           DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+          DOCKER_IMAGE_NAME: ${{ secrets.DOCKER_IMAGE_NAME }}
+          APP_DIR_PRODUCTION: ${{ secrets.APP_DIR_PRODUCTION }}
+          APP_DIR_DEVELOPMENT: ${{ secrets.APP_DIR_DEVELOPMENT }}
         with:
           host: ${{ secrets.SERVER_SSH_HOST }}
           username: ${{ secrets.SERVER_SSH_USERNAME }}
           key: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
           port: ${{ secrets.SERVER_SSH_PORT }}
-          envs: APPLICATION_PROD,APPLICATION_DEV,APPLICATION_SECRET,ENVIRONMENT,IMAGE_TAG,REPO_OWNER,DOCKER_IMAGE_NAME,DOCKERHUB_TOKEN,DOCKERHUB_USERNAME
+          envs: APPLICATION_PROD,APPLICATION_DEV,APPLICATION_SECRET,ENVIRONMENT,IMAGE_TAG,REPO_OWNER,DOCKER_IMAGE_NAME,DOCKERHUB_TOKEN,DOCKERHUB_USERNAME,APP_DIR_PRODUCTION,APP_DIR_DEVELOPMENT
           script_path: scripts/cd-dev.sh
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: SSH 접속 및 스크립트 실행
uses: appleboy/ssh-action@v1
env:
APPLICATION_PROD: ${{ secrets.APPLICATION_PROD }}
APPLICATION_DEV: ${{ secrets.APPLICATION_DEV }}
APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
with:
host: ${{ secrets.SERVER_SSH_HOST }}
username: ${{ secrets.SERVER_SSH_USERNAME }}
key: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_SSH_PORT }}
envs: APPLICATION_PROD,APPLICATION_DEV,APPLICATION_SECRET,ENVIRONMENT,IMAGE_TAG,REPO_OWNER,DOCKER_IMAGE_NAME,DOCKERHUB_TOKEN,DOCKERHUB_USERNAME
script_path: scripts/cd-dev.sh
- name: SSH 접속 및 스크립트 실행
uses: appleboy/ssh-action@v1
env:
APPLICATION_PROD: ${{ secrets.APPLICATION_PROD }}
APPLICATION_DEV: ${{ secrets.APPLICATION_DEV }}
APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_IMAGE_NAME: ${{ secrets.DOCKER_IMAGE_NAME }}
APP_DIR_PRODUCTION: ${{ secrets.APP_DIR_PRODUCTION }}
APP_DIR_DEVELOPMENT: ${{ secrets.APP_DIR_DEVELOPMENT }}
with:
host: ${{ secrets.SERVER_SSH_HOST }}
username: ${{ secrets.SERVER_SSH_USERNAME }}
key: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_SSH_PORT }}
envs: APPLICATION_PROD,APPLICATION_DEV,APPLICATION_SECRET,ENVIRONMENT,IMAGE_TAG,REPO_OWNER,DOCKER_IMAGE_NAME,DOCKERHUB_TOKEN,DOCKERHUB_USERNAME,APP_DIR_PRODUCTION,APP_DIR_DEVELOPMENT
script_path: scripts/cd-dev.sh
🤖 Prompt for AI Agents
In @.github/workflows/cd-dev.yml around lines 46 - 61, Add the missing
APP_DIR_PRODUCTION and APP_DIR_DEVELOPMENT environment variables to the SSH
action: define them in the env: block (e.g., APP_DIR_PRODUCTION: ${{
secrets.APP_DIR_PRODUCTION }} and APP_DIR_DEVELOPMENT: ${{
secrets.APP_DIR_DEVELOPMENT }}) and include their names in the with: envs
comma-separated list (alongside APPLICATION_PROD,APPLICATION_DEV,...). This
ensures the deployment script referenced by script_path: cd-dev.sh receives the
required APP_DIR_PRODUCTION and APP_DIR_DEVELOPMENT values that it validates.

Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: SSH 액션의 script_path 속성은 로컬 파일 경로를 원격 서버로 전송하는 용도입니다. 그러나 현재 구조에서는 scripts/cd-dev.sh 파일이 원격 서버에 존재해야 하는데, 이 파일을 원격 서버로 전송하는 단계가 없습니다.

다음 두 가지 방법 중 하나를 선택해야 합니다:

  1. script 속성을 사용하여 스크립트 내용을 직접 전달
  2. 또는 scp/rsync로 스크립트를 먼저 전송한 후 실행

script 속성 사용 예시:

script: |
  bash << 'EOF'
  $(cat scripts/cd-dev.sh의 내용)
  EOF
Suggested change
script_path: scripts/cd-dev.sh
script: |
bash << 'EOF'
# scripts/cd-dev.sh의 내용을 여기에 그대로 붙여넣습니다.
# 예: 기존 scripts/cd-dev.sh 파일의 모든 쉘 명령들
EOF

Copilot uses AI. Check for mistakes.
- name: Deployment Summary
if: always()
run: |
echo "### Deployment Summary :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Environment:** ${{ env.ENVIRONMENT }}" >> $GITHUB_STEP_SUMMARY
echo "- **Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image:** ${{ secrets.DOCKER_IMAGE_NAME }}:${{ env.ENVIRONMENT }}-latest" >> $GITHUB_STEP_SUMMARY
echo "- **Server:** ${{ secrets.SERVER_SSH_HOST }}" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Shellcheck 스타일 경고를 개선할 수 있습니다.

Deployment Summary 스크립트에서 여러 개의 echo 명령이 같은 파일로 리다이렉션됩니다. 이를 하나의 블록으로 묶으면 더 효율적이고 가독성이 좋아집니다.

♻️ 제안 수정사항
       - name: Deployment Summary
         if: always()
         run: |
-          echo "### Deployment Summary :rocket:" >> $GITHUB_STEP_SUMMARY
-          echo "" >> $GITHUB_STEP_SUMMARY
-          echo "- **Environment:** ${{ env.ENVIRONMENT }}" >> $GITHUB_STEP_SUMMARY
-          echo "- **Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
-          echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
-          echo "- **Image:** ${{ secrets.DOCKER_IMAGE_NAME }}:${{ env.ENVIRONMENT }}-latest" >> $GITHUB_STEP_SUMMARY
-          echo "- **Server:** ${{ secrets.SERVER_SSH_HOST }}" >> $GITHUB_STEP_SUMMARY
-          echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
+          {
+            echo "### Deployment Summary :rocket:"
+            echo ""
+            echo "- **Environment:** ${{ env.ENVIRONMENT }}"
+            echo "- **Branch:** ${{ github.ref_name }}"
+            echo "- **Commit:** ${{ github.sha }}"
+            echo "- **Image:** ${{ secrets.DOCKER_IMAGE_NAME }}:${{ env.ENVIRONMENT }}-latest"
+            echo "- **Server:** ${{ secrets.SERVER_SSH_HOST }}"
+            echo "- **Status:** ${{ job.status }}"
+          } >> "$GITHUB_STEP_SUMMARY"
🧰 Tools
🪛 actionlint (1.7.10)

65-65: shellcheck reported issue in this script: SC2086:info:1:43: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:2:12: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:3:53: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:4:48: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:5:43: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:6:87: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:7:56: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2086:info:8:43: Double quote to prevent globbing and word splitting

(shellcheck)


65-65: shellcheck reported issue in this script: SC2129:style:1:1: Consider using { cmd1; cmd2; } >> file instead of individual redirects

(shellcheck)

🤖 Prompt for AI Agents
In @.github/workflows/cd-dev.yml around lines 65 - 73, Multiple sequential echo
statements appending to $GITHUB_STEP_SUMMARY are inefficient and trigger
shellcheck style warnings; replace the repeated echo lines with a single
here-document that appends the entire Deployment Summary to $GITHUB_STEP_SUMMARY
(use an unquoted heredoc like cat <<EOF >> $GITHUB_STEP_SUMMARY so environment
variables like ${env.ENVIRONMENT} and ${github.sha} expand) to improve
readability and reduce subprocess calls, keeping the same content and ordering
as the original echo lines.

185 changes: 185 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: CI

on:
pull_request:
branches:
- develop
- main

workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
type: choice
options:
- dev
- prod

workflow_call:
inputs:
environment:
description: 'Deploy environment (optional)'
required: false
type: string
outputs:
environment:
description: "Determined environment"
value: ${{ jobs.build-and-test.outputs.environment }}
repo_owner:
description: "Repository owner"
value: ${{ jobs.build-and-test.outputs.repo_owner }}
image_tag:
description: "Docker image tag"
value: ${{ jobs.build-and-test.outputs.image_tag }}

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # 이전 실행 취소

jobs:
# Job 1: 빌드 및 테스트 (한 번만 실행)
build-and-test:
runs-on: self-hosted

permissions:
contents: read
checks: write
pull-requests: write

outputs:
environment: ${{ steps.set-env.outputs.environment }}
repo_owner: ${{ steps.set-env.outputs.repo_owner }}
image_tag: ${{ steps.set-env.outputs.image_tag }}

strategy:
matrix:
platform: [ linux/amd64, linux/arm64 ]
include:
- platform: linux/amd64
tag-suffix: amd64
- platform: linux/arm64
tag-suffix: arm64
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: matrix strategy와 outputs 설정이 충돌합니다. matrix를 사용하면 job이 여러 번 실행되지만(linux/amd64, linux/arm64), outputs는 마지막 실행 결과만 저장됩니다. 따라서 environment, repo_owner, image_tag 값이 의도와 다르게 설정될 수 있습니다.

environment, repo_owner, image_tag는 플랫폼과 무관하게 동일한 값이므로, 별도의 job으로 분리하거나 matrix를 사용하지 않는 초기 단계에서 설정해야 합니다. 예를 들어:

  • 빌드 전 준비 job을 별도로 만들어 outputs 설정
  • 또는 create-manifest job에서 outputs를 재설정

Copilot uses AI. Check for mistakes.

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set deployment environment
id: set-env
run: |
INPUT_ENV="${{ inputs.environment }}"
# PR일 때는 'test' 환경으로 설정
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "Running in PR mode - Setting test environment for build"
ENVIRONMENT="test"
elif [[ -n "$INPUT_ENV" ]]; then
ENVIRONMENT="$INPUT_ENV"
# CD에서 호출한 경우 branch에 따라서 환경 결정
elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
ENVIRONMENT="prod"
elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
ENVIRONMENT="dev"
else
ENVIRONMENT="test"
fi
Comment on lines +77 to +87
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: 환경 변수 검증 로직에 else 분기가 누락되어 있습니다. 조건문이 elif로만 연결되어 있어, 마지막 elif 조건도 만족하지 않을 경우 ENVIRONMENT 변수가 설정되지 않은 상태로 다음 단계로 진행될 수 있습니다.

마지막 elif를 else로 변경하거나, 조건문 이후에 ENVIRONMENT 변수 검증을 추가해야 합니다.

Copilot uses AI. Check for mistakes.
echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT
REPO_OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "repo_owner=${REPO_OWNER}" >> $GITHUB_OUTPUT
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
IMAGE_TAG="${ENVIRONMENT}-${SHORT_SHA}"
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
Comment on lines +68 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

환경 결정 로직에 else 절이 누락되었습니다.

Line 77-79의 조건문에 else 절이 없어, 특정 경우에 ENVIRONMENT 변수가 설정되지 않을 수 있습니다. Line 86의 else가 전체 if-elif 체인의 최종 else로 작동하므로 로직상 문제는 없지만, 가독성을 위해 구조를 명확히 하는 것이 좋습니다.

♻️ 제안하는 수정안
           # PR일 때는 'test' 환경으로 설정
           if [[ "${{ github.event_name }}" == "pull_request" ]]; then
             echo "Running in PR mode - Setting test environment for build"
             ENVIRONMENT="test"
           elif [[ -n "$INPUT_ENV" ]]; then
             ENVIRONMENT="$INPUT_ENV"
-          
-          # CD에서 호출한 경우 branch에 따라서 환경 결정
           elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
             ENVIRONMENT="prod"
           elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
             ENVIRONMENT="dev"
           else
             ENVIRONMENT="test"
           fi
🧰 Tools
🪛 actionlint (1.7.10)

70-70: shellcheck reported issue in this script: SC2086:info:19:38: Double quote to prevent globbing and word splitting

(shellcheck)


70-70: shellcheck reported issue in this script: SC2086:info:22:36: Double quote to prevent globbing and word splitting

(shellcheck)


70-70: shellcheck reported issue in this script: SC2086:info:26:34: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
In @.github/workflows/ci.yml around lines 68 - 96, The if/elif chain inside the
"set-env" step can leave ENVIRONMENT unset due to a missing else after the
INPUT_ENV check; ensure every branch sets ENVIRONMENT by adding an explicit else
or restructuring the conditionals so that when INPUT_ENV is empty the subsequent
branch checks (github.ref for main/develop) always run; update the logic around
INPUT_ENV, ENVIRONMENT and the github.event_name/github.ref checks so
ENVIRONMENT is always assigned before it's echoed to GITHUB_OUTPUT (refer to
variables INPUT_ENV, ENVIRONMENT, github.event_name, github.ref and the
"set-env" step).

- name: Setup JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'corretto'
cache: gradle

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

- name: Compile Check
run: ./gradlew compileJava compileTestJava

- name: Run Tests
run: ./gradlew test

- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: build/test-results/test/*.xml
check_name: "Backend Test Results"

- name: Build JAR
run: ./gradlew bootJar

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in 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@v6
with:
context: .
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 두 개의 Dockerfile이 존재하는데 (docker/kyeoungwoon/dockerfile과 docker/app/dockerfile), 목적과 사용처가 명확하지 않습니다.

  • kyeoungwoon/dockerfile: CI에서 사용 (eclipse-temurin 기반)
  • app/dockerfile: 사용처 불명확 (amazoncorretto 기반)

하나의 Dockerfile만 유지하거나, 각 파일의 목적을 README나 주석으로 명확히 문서화해야 합니다. 또한 CI 워크플로우에서 docker/kyeoungwoon/dockerfile을 사용하는 이유를 설명하는 주석을 추가하는 것이 좋습니다.

Suggested change
context: .
context: .
# CI용 이미지 빌드를 위해 eclipse-temurin 기반 Dockerfile 사용
# (amazoncorretto 기반 docker/app/dockerfile은 런타임/기타 용도로 별도 관리)

Copilot uses AI. Check for mistakes.
file: docker/kyeoungwoon/dockerfile
push: true
tags: |
${{ secrets.DOCKER_IMAGE_NAME }}:${{ steps.set-env.outputs.image_tag }}-${{ matrix.tag-suffix }}
platforms: ${{ matrix.platform }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}

Comment on lines 141 to 153
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: PR 모드일 때 Docker 이미지를 빌드하고 푸시하는 것은 불필요한 리소스 사용입니다. PR은 코드 검증 목적이므로 테스트만 실행하고, 이미지 빌드와 푸시는 실제 배포가 필요한 경우에만 수행하는 것이 효율적입니다.

다음과 같이 조건부로 실행하도록 개선할 수 있습니다:

- name: Build and push Docker image
  if: github.event_name != 'pull_request'
  uses: docker/build-push-action@v6

또는 PR의 경우 push: false로 설정하여 빌드만 검증하도록 할 수 있습니다.

Copilot uses AI. Check for mistakes.
# Job 3: Multi-arch manifest 생성
create-manifest:
needs: [ build-and-test ]
runs-on: self-hosted
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Create and push manifest
run: |
IMAGE_TAG="${{ needs.build-and-test.outputs.image_tag }}"
ENVIRONMENT="${{ needs.build-and-test.outputs.environment }}"
# 특정 커밋용 manifest 생성
docker buildx imagetools create -t ${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG} \
${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG}-amd64 \
${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG}-arm64
# 환경별 latest manifest 생성
docker buildx imagetools create -t ${{ secrets.DOCKER_IMAGE_NAME }}:${ENVIRONMENT}-latest \
${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG}-amd64 \
${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG}-arm64
echo "✅ Created manifests:"
echo " - ${{ secrets.DOCKER_IMAGE_NAME }}:${IMAGE_TAG}"
echo " - ${{ secrets.DOCKER_IMAGE_NAME }}:${ENVIRONMENT}-latest"
- name: Build Summary
if: always()
run: |
echo "### Build Summary :package:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Environment:** ${{ steps.set-env.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ steps.set-env.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
Comment on lines +159 to +160
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Build Summary 단계가 잘못된 위치에 있습니다. steps.set-env는 build-and-test job에 속하지만, 현재 이 단계는 create-manifest job에 있습니다. steps.set-env.outputs는 다른 job에서 접근할 수 없으므로 항상 빈 값이 출력됩니다.

needs를 통해 전달받은 값을 사용해야 합니다:

echo "- **Environment:** ${{ needs.build-and-test.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ needs.build-and-test.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
Suggested change
echo "- **Environment:** ${{ steps.set-env.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ steps.set-env.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Environment:** ${{ needs.build-and-test.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ needs.build-and-test.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY

Copilot uses AI. Check for mistakes.
echo "- **Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
Comment on lines +154 to +161
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Build Summary 단계에서 존재하지 않는 step 출력을 참조하고 있습니다.

steps.set-env.outputs를 참조하고 있지만, create-manifest 잡에는 set-env라는 id를 가진 step이 없습니다. build-and-test 잡의 출력을 참조해야 합니다.

🔧 수정 방법 (create-manifest 잡을 유지하는 경우)
       - name: Build Summary
         if: always()
         run: |
           echo "### Build Summary :package:" >> $GITHUB_STEP_SUMMARY
           echo "" >> $GITHUB_STEP_SUMMARY
-          echo "- **Environment:** ${{ steps.set-env.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
-          echo "- **Image Tag:** ${{ steps.set-env.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
+          echo "- **Environment:** ${{ needs.build-and-test.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
+          echo "- **Image Tag:** ${{ needs.build-and-test.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
           echo "- **Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY

Static analysis 도구의 지적사항을 기반으로 함.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Build Summary
if: always()
run: |
echo "### Build Summary :package:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Environment:** ${{ steps.set-env.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ steps.set-env.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
- name: Build Summary
if: always()
run: |
echo "### Build Summary :package:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Environment:** ${{ needs.build-and-test.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "- **Image Tag:** ${{ needs.build-and-test.outputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
🧰 Tools
🪛 actionlint (1.7.10)

186-186: property "set-env" is not defined in object type {}

(expression)

🤖 Prompt for AI Agents
In @.github/workflows/ci.yml around lines 184 - 191, The Build Summary step is
referencing nonexistent step outputs (steps.set-env.outputs.environment and
steps.set-env.outputs.image_tag); update the echo lines in the Build Summary
step to use the outputs from the build-and-test job instead
(needs.build-and-test.outputs.environment and
needs.build-and-test.outputs.image_tag), and ensure the create-manifest job
declares needs: build-and-test so those job outputs are available.

13 changes: 7 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ application-*.yml

.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
Expand All @@ -33,10 +30,8 @@ bin/
*.iml
*.ipr

!**/src/main/**/out/
!**/src/test/**/out/
# 빌드 결과물인 out은 무시해야 하나, 소스코드 내에 있는건 지켜야 함
/out/
out/

### NetBeans ###
/nbproject/private/
Expand Down Expand Up @@ -237,3 +232,9 @@ $RECYCLE.BIN/
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/java,intellij+all,macos,windows,linux

!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
!**/src/main/**/out/
!**/src/test/**/out/
45 changes: 45 additions & 0 deletions docker/app/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
FROM amazoncorretto:21-alpine-jre

LABEL maintainer="UMC Product Team"
LABEL description="UMC Product Team Official SpringBoot Backend"

# 작업 디렉토리 설정
WORKDIR /app

# curl 설치 (헬스체크용) + non-root 유저 생성
RUN apk add --no-cache curl && \
addgroup -S spring && \
adduser -S spring -G spring && \
mkdir -p /app/logs && \
chown -R spring:spring /app
Comment on lines +8 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

패키지 버전 고정을 고려하세요.

curl 패키지가 버전 고정 없이 설치되고 있습니다. 빌드 재현성과 보안을 위해 특정 버전을 명시하는 것이 권장됩니다.

♻️ 제안하는 수정안
-RUN apk add --no-cache curl && \
+RUN apk add --no-cache curl=8.5.0-r0 && \
     addgroup -S spring && \
     adduser -S spring -G spring && \
     mkdir -p /app/logs && \
     chown -R spring:spring /app

참고: Alpine 버전에 따라 사용 가능한 curl 버전이 다를 수 있으므로 apk search curl 명령으로 확인 후 적절한 버전을 지정하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apk add --no-cache curl && \
addgroup -S spring && \
adduser -S spring -G spring && \
mkdir -p /app/logs && \
chown -R spring:spring /app
RUN apk add --no-cache curl=8.5.0-r0 && \
addgroup -S spring && \
adduser -S spring -G spring && \
mkdir -p /app/logs && \
chown -R spring:spring /app
🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 10-10: Pin versions in apk add. Instead of apk add <package> use apk add <package>=<version>

(DL3018)

🤖 Prompt for AI Agents
In @docker/app/dockerfile around lines 10 - 14, The RUN step installs curl
without a pinned version; change the Dockerfile RUN line that currently uses
"apk add --no-cache curl" to pin a specific curl package version (e.g., "apk add
--no-cache curl=<version>" or the exact release string like
"curl=<pkg>-r<revision>") so builds are reproducible and secure, and verify the
correct available version for your Alpine base with "apk search curl" before
setting the version.

Comment on lines +7 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

curl 버전을 고정하고 중복 디렉토리 생성 제거 필요

이 섹션에는 두 가지 개선이 필요합니다:

  1. Line 8에서 curl 패키지 버전을 고정하지 않았습니다. 재현 가능한 빌드를 위해 버전을 명시해야 합니다.
  2. Line 11에서 /app/logs 디렉토리를 생성했지만, Line 19에서 동일한 작업을 다시 수행합니다.
🔧 제안하는 수정사항
-RUN apk add --no-cache curl && \
+RUN apk add --no-cache curl=8.5.0-r0 && \
     addgroup -S spring && \
     adduser -S spring -G spring && \
     mkdir -p /app/logs && \
     chown -R spring:spring /app

그리고 Line 18-19를 제거하세요 (중복):

-# 로그 디렉토리 생성 및 권한 설정
-RUN mkdir -p /app/logs && chown -R spring:spring /app/logs

참고: curl 버전은 Alpine 버전에 따라 다를 수 있으므로, apk search curl로 사용 가능한 버전을 확인하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# curl 설치 (헬스체크용) + non-root 유저 생성
RUN apk add --no-cache curl && \
addgroup -S spring && \
adduser -S spring -G spring && \
mkdir -p /app/logs && \
chown -R spring:spring /app
# curl 설치 (헬스체크용) + non-root 유저 생성
RUN apk add --no-cache curl=8.5.0-r0 && \
addgroup -S spring && \
adduser -S spring -G spring && \
mkdir -p /app/logs && \
chown -R spring:spring /app
🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 8-8: Pin versions in apk add. Instead of apk add <package> use apk add <package>=<version>

(DL3018)

🤖 Prompt for AI Agents
In @docker/app/dockerfile around lines 7 - 12, Pin the curl package version in
the RUN apk add command (e.g., apk add --no-cache curl=<VERSION>) to ensure
reproducible builds and remove the duplicate /app/logs creation by deleting the
later mkdir -p /app/logs (the second occurrence); keep a single mkdir -p
/app/logs and its chown -R spring:spring in the RUN block that also creates the
spring user/group (the RUN line containing apk add, addgroup -S spring, adduser
-S spring -G spring, mkdir -p /app/logs, chown -R spring:spring), and replace
<VERSION> with the appropriate Alpine curl package version found via apk search.


# JAR 파일 복사 (GitHub Actions에서 빌드된 JAR)
# docker build 시 context에서 복사됨
COPY --chown=spring:spring build/libs/*.jar app.jar

# spring 유저로 전환
USER spring:spring

# 애플리케이션 포트
EXPOSE 8080

# Health check 설정
# curl은 Alpine Linux에 기본 포함됨
HEALTHCHECK --interval=30s \
--timeout=3s \
--start-period=40s \
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 헬스체크의 start-period가 40초로 설정되어 있는데, Spring Boot 애플리케이션의 일반적인 시작 시간을 고려하면 너무 짧을 수 있습니다. 특히 JPA, Flyway, 외부 연동 등이 있는 경우 60초 이상이 필요할 수 있습니다.

실제 애플리케이션 시작 시간을 측정한 후 적절한 값으로 조정하는 것을 권장합니다. 시작 시간이 부족하면 컨테이너가 정상임에도 불구하고 unhealthy로 판단될 수 있습니다.

Suggested change
--start-period=40s \
--start-period=60s \

Copilot uses AI. Check for mistakes.
--retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Health check URL이 /actuator/health로 하드코딩되어 있지만, 실제 애플리케이션에서 해당 엔드포인트가 활성화되어 있는지 확인이 필요합니다. Spring Boot Actuator가 의존성에 포함되어 있지 않으면 health check가 계속 실패하여 컨테이너가 unhealthy 상태로 표시됩니다.

다음 중 하나를 확인해야 합니다:

  1. build.gradle.kts에 spring-boot-starter-actuator 의존성이 있는지 확인
  2. 또는 health check를 주석 처리하거나 다른 엔드포인트로 변경
Suggested change
# curl은 Alpine Linux에 기본 포함됨
HEALTHCHECK --interval=30s \
--timeout=3s \
--start-period=40s \
--retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 주의: 아래 헬스체크는 Spring Boot Actuator의 /actuator/health 엔드포인트가 활성화되어 있어야만 동작합니다.
# - build.gradle.kts에 spring-boot-starter-actuator 의존성이 포함되어 있는지
# - management.endpoints.web.exposure.include 등에 health 엔드포인트가 노출되어 있는지
# 를 확인한 뒤, 실제 애플리케이션의 헬스 엔드포인트에 맞게 수정 후 주석을 해제해 사용하세요.
# 예시:
# HEALTHCHECK --interval=30s \
# --timeout=3s \
# --start-period=40s \
# --retries=3 \
# CMD curl -f http://localhost:8080/actuator/health || exit 1

Copilot uses AI. Check for mistakes.

# JVM 옵션 및 애플리케이션 실행
# 환경변수로 힙 메모리 설정 가능 (기본값: dev 환경)
ENV JAVA_OPTS="-Xms1400m -Xmx1400m"
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: JVM 메모리 설정이 하드코딩되어 있어 환경별 유연성이 떨어집니다. JAVA_OPTS를 ENV로 설정하면 런타임에 변경할 수 없으며, 개발/프로덕션 환경에서 다른 메모리 설정이 필요할 수 있습니다.

ARG를 사용하여 빌드 시점에 설정하거나, docker-compose.yml에서 환경 변수로 오버라이드할 수 있도록 변경하는 것을 권장합니다:

ENV JAVA_OPTS="-Xms1400m -Xmx1400m"
# docker-compose.yml에서 environment로 오버라이드 가능

또는 kyeoungwoon dockerfile처럼 MaxRAMPercentage를 사용하여 컨테이너 메모리에 비례하도록 설정하는 것이 더 유연합니다.

Suggested change
# 환경변수로 힙 메모리 설정 가능 (기본값: dev 환경)
ENV JAVA_OPTS="-Xms1400m -Xmx1400m"
# 기본 JVM 옵션: 컨테이너 메모리에 비례하여 힙 메모리 설정
ARG JAVA_OPTS="-XX:InitialRAMPercentage=30.0 -XX:MaxRAMPercentage=75.0"
ENV JAVA_OPTS=${JAVA_OPTS}

Copilot uses AI. Check for mistakes.

ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} \
-XX:+UseG1GC \
-XX:MaxMetaspaceSize=256m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/logs/heapdump.hprof \
-Duser.timezone=Asia/Seoul \
-Djava.security.egd=file:/dev/./urandom \
-jar app.jar"]
40 changes: 40 additions & 0 deletions docker/kyeoungwoon/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# =================================
# Run stage only
# =================================
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app

LABEL maintainer="UMC PRODUCT TEAM SERVER TEAM"
LABEL description="UMC PRODUCT Official SpringBoot Backend Server"

# Create non-root user
RUN groupadd -r spring && useradd -r -g spring spring

# 이미 build된 jar 파일을 docker 안으로 복사
COPY --chown=spring:spring build/libs/*.jar app.jar
RUN chmod 444 app.jar

# 로그 디렉토리 생성 및 권한 설정
RUN mkdir -p /app/logs && chown -R spring:spring /app/logs

# Switch to non-root user
USER spring:spring

# Expose application port
EXPOSE 8080

# JVM options
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
-XX:+ExitOnOutOfMemoryError \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof \
-Duser.timezone=Asia/Seoul \
-Djava.security.egd=file:/dev/./urandom"

# Use exec form with sh -c so JAVA_OPTS is expanded and signals forwarded
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]

# Optional healthcheck (uncomment if actuator/health endpoint exists)
# HEALTHCHECK --interval=30s --timeout=5s CMD curl -f http://localhost:8080/actuator/health || exit 1
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: HEALTHCHECK 주석에 actuator/health 엔드포인트 존재 확인이 필요하다고 명시되어 있는데, build.gradle.kts를 확인한 결과 spring-boot-starter-actuator가 포함되어 있으므로 주석을 해제해도 됩니다.

다만 health check를 활성화하려면 다음을 확인해야 합니다:

  1. application.yml에서 actuator health 엔드포인트가 노출되어 있는지 확인
  2. Security 설정에서 /actuator/health가 인증 없이 접근 가능한지 확인

Copilot uses AI. Check for mistakes.
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
Loading
Loading