Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
98b7c03
fix: deprecated된 base image를 eclipse-temurin:17-jdk로 변경
Hexeong Nov 4, 2025
83d611f
refactor: scp 파일 전송하는 방식에서 GHCR로 push/pull하도록 변경
Hexeong Nov 4, 2025
216e83a
fix: GHCR image 제거시 Org의 GITHUB_TOKEN 사용하도록 변경
Hexeong Nov 4, 2025
898d594
refactor : scp 파일 전송하는 방식에서 GHCR로 push/pull하도록 prod-cd.yml과 docker-co…
Hexeong Nov 4, 2025
a76e446
fix: prod 인스턴스 old image 이름 통일
Hexeong Nov 4, 2025
59c0116
fix: prod-cd.yml StrictHostKeyChecking 옵션 문법 오류 수정
Hexeong Nov 5, 2025
c86901a
fix: prod-cd.yml StrictHostKeyChecking 옵션 문법 오류 수정
Hexeong Nov 6, 2025
595c6aa
fix: dev-cd.yml Old images 정리 작업 중 이미지 이름 불일치 문제 해결
Hexeong Nov 6, 2025
63a16fa
chore: 마지막 줄 개행 추가
Hexeong Nov 6, 2025
ab3054e
chore: 마지막 줄 개행 추가
Hexeong Nov 6, 2025
01114c3
feat: stage 인스턴스에 대한 최신 이미지 5개 유지 기능 및 old 이미지 제거 기능 추가
Hexeong Nov 10, 2025
229d9aa
chore: 중복된 환경변수 지정 제거
Hexeong Nov 10, 2025
1b57e74
chore: 중복된 pem키 생성 로직 제거
Hexeong Nov 10, 2025
0c431df
fix: 잘못된 pem키 이름 수정
Hexeong Nov 10, 2025
7ae94b1
refactor: 원격 호스트에서 pull할 경우, 최소 권한으로 실행하도록 Github App으로 임시토큰 발급하도록 수정
Hexeong Nov 15, 2025
be3d47a
Merge remote-tracking branch 'origin/develop' into refactor/529-short…
Hexeong Nov 15, 2025
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
162 changes: 106 additions & 56 deletions .github/workflows/dev-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout the code
Expand All @@ -18,81 +19,130 @@ jobs:
token: ${{ secrets.SUBMODULE_ACCESS_TOKEN }}
submodules: true

# --- Java, Gradle 설정 ---
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

# Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0

uses: gradle/actions/setup-gradle@v3
- name: Grant execute permission for Gradle wrapper(gradlew)
run: chmod +x ./gradlew

- name: Build with Gradle
run: ./gradlew bootJar

- name: Copy jar file to remote
uses: appleboy/scp-action@master
# --- Docker 설정 ---
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
source: "./build/libs/*.jar"
target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/"

- name: Copy docker file to remote
uses: appleboy/scp-action@master
platforms: linux/arm64
- name: Log in to GitHub Container Registry (GHCR)
uses: docker/login-action@v3
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
source: "./Dockerfile"
target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/"
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Copy docker compose file to remote
uses: appleboy/scp-action@master
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
source: "./docker-compose.dev.yml"
target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/"
# --- 2. 이미지 메타데이터(이름, 태그) 정의 ---
# 빌드/푸시 단계와 SSH 단계에서 공통으로 사용할 변수를 미리 정의합니다.
- name: Define image name and tag
id: image_meta
run: |
OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
Copy link
Member

Choose a reason for hiding this comment

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

github.repository_owner 로 설정하면 레포 소유자 이름(e.g. whqtker)으로 고정되는 건가요 ? 만약 그렇다면, solid-connection 과 같이 사용자에 종속되지 않는 이름으로 설정하는 게 좋을 거 같습니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Organization에 속한 레포의 워크플로우 실행의 경우 github.repository_owner은 Organization 이름인 solid-connection이 됩니다! 때문에 사용자 이름으로 종속되지는 않아 해당 문제는 없을 것 같아요!

Copy link
Member

Choose a reason for hiding this comment

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

확인했습니다 ~!

IMAGE_TAG=$(date +'%Y%m%d-%H%M%S')
echo "image_name=ghcr.io/${OWNER_LOWERCASE}/solid-connection-dev" >> $GITHUB_OUTPUT
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT

- name: Copy alloy config file to remote
uses: appleboy/scp-action@master
# --- 3. Docker 이미지 빌드, 푸시, 캐시 ---
# 'docker/build-push-action'을 사용하여 캐시 옵션을 적용합니다.
- name: Build, push, and cache Docker image
uses: docker/build-push-action@v5
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
source: "./docs/infra-config/config.alloy"
target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/"
context: .
platforms: linux/arm64
push: true
tags: ${{ format('{0}:{1}', steps.image_meta.outputs.image_name, steps.image_meta.outputs.image_tag) }}
cache-from: type=registry,ref=${{ steps.image_meta.outputs.image_name }}:buildcache
cache-to: type=registry,ref=${{ steps.image_meta.outputs.image_name }}:buildcache,mode=max

- name: Copy nginx config to remote
uses: appleboy/scp-action@master
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
source: "./docs/infra-config/nginx.dev.conf"
target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/nginx"
rename: "default.conf"
# --- 4. 설정 파일들만 scp로 전송 ---
- name: Copy config files to remote
run: |
echo "${{ secrets.DEV_PRIVATE_KEY }}" > deploy_key.pem
chmod 600 deploy_key.pem

scp -i deploy_key.pem \
-o StrictHostKeyChecking=no \
./docker-compose.dev.yml \
./docs/infra-config/config.alloy \
./docs/infra-config/nginx.dev.conf \
${{ secrets.DEV_USERNAME }}@${{ secrets.DEV_HOST }}:/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/

# --- 5. 서버에서 'docker pull' 및 서비스 재시작 ---
- name: Run docker compose and apply nginx config
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_PRIVATE_KEY }}
script_stop: true
script: |
sudo cp /home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/nginx/default.conf /etc/nginx/conf.d/default.conf
run: |
echo "${{ secrets.DEV_PRIVATE_KEY }}" > deploy_key_ssh.pem
chmod 600 deploy_key_ssh.pem
Copy link
Contributor

Choose a reason for hiding this comment

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

위에서 생성한 key를 그대로 사용해도 되지 않나요..?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

해당 내용 반영했습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

이름도 바꿔주셔야 할 것 같아요! deploy_key.pem으로 저장이 되었는데 deploy_key_ssh.pem을 사용하고 있습니당

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영했습니다!


ssh -i deploy_key_ssh.pem \
-o StrictHostKeyChecking=no \
${{ secrets.DEV_USERNAME }}@${{ secrets.DEV_HOST }} \
'
set -e

# 1. 변수를 'image_meta' 단계의 출력값에서 가져옴
export OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
export IMAGE_TAG_ONLY=${{ steps.image_meta.outputs.image_tag }}
export FULL_IMAGE_NAME="ghcr.io/${OWNER_LOWERCASE}/solid-connection-dev:${IMAGE_TAG_ONLY}"

# 2. 서버가 GHCR에 로그인 (pull 받기 위해)
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
Copy link

@coderabbitai coderabbitai bot Nov 4, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

GHCR 로그인 시 사용자명 대소문자 불일치 문제가 있습니다.

dev-cd.yml도 prod-cd.yml과 동일한 문제가 있습니다.

Line 100에서 docker login 시 사용자명으로 ${{ github.repository_owner }}를 사용하고 있으나, 이미지 태그는 소문자로 변환되어 있습니다:

  • Line 52: OWNER_LOWERCASE=$(echo ... | tr '[:upper:]' '[:lower:]')
  • Line 54: 이미지명에 OWNER_LOWERCASE 사용

GitHub 저장소 소유자명에 대문자가 있으면 로그인 실패 또는 권한 오류 발생 가능성이 있습니다.

수정 방법:

  # 2. 서버가 GHCR에 로그인 (pull 받기 위해)
- echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
+ OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${OWNER_LOWERCASE} --password-stdin
📝 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
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${OWNER_LOWERCASE} --password-stdin
🤖 Prompt for AI Agents
.github/workflows/dev-cd.yml around line 100: the docker login uses ${{
github.repository_owner }} which may contain uppercase letters while the image
tag and OWNER_LOWERCASE are used elsewhere, causing GHCR auth failures; change
the login username to the already-computed OWNER_LOWERCASE variable (or compute
a lowercase owner just before login) so the docker login user matches the
lowercase image namespace, and apply the same change in prod-cd.yml.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@coderabbitai

이전에 lower_case로 통일하도록 이미지 메타데이터 정의 스텝에서 전처리를 해두었기에 문제가 나지 않을 것 같은데?

심지어 내 닉네임이 Hexeong인데, fork 레포에서 깃헙 액션을 실행시켰을 때에 문제가 발생하지 않았었어

Copy link

Choose a reason for hiding this comment

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

@Hexeong

맞습니다! 제가 잘못 지적했네요. 죄송합니다. 🙇

  1. Docker/GHCR 로그인은 사용자명 대소문자를 구분하지 않습니다.

    • Line 100의 docker login ghcr.io -u ${{ github.repository_owner }}Hexeong이든 hexeong이든 정상적으로 인증됩니다.
  2. 이미지 경로는 올바르게 소문자를 사용하고 있습니다.

    • Line 95에서 OWNER_LOWERCASE를 재계산하고
    • Line 97에서 FULL_IMAGE_NAME에 소문자 버전을 사용하여
    • Line 103의 docker pull이 정상 작동합니다.

실제 테스트에서 문제가 없었다는 것이 이를 증명하네요. 불필요한 수정 제안을 드려 혼란을 드려 죄송합니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

지금 쓰기가 가능한 토큰이 서버로 전송되는데 보안상 취약하지 않을까요...? 읽기 전용 토큰을 따로 발급해서 사용하는 게 좋아보입니당

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SSH를 통해 GHCR에 접근하는 이 방식을 적용하려면, SUBMODULE_ACCESS_TOKEN처럼 Fine-grained Token을 사용하는 것이 보안상 가장 좋다고 생각합니다!

하지만 이 방식을 사용하려면 GHCR 읽기 권한이 있는 분이 직접 토큰을 발급해야 하는데, 이는 특정 계정에 파이프라인이 의존하게 되는 문제가 있어 망설여졌습니다.

그래서 일단은 임시로 GITHUB_TOKEN을 사용하는 방식을 적용했습니다만, 혹시 이 '개인 종속성' 문제를 해결할 더 좋은 방법이 있다면 의견 부탁드립니다! @lsy1307

Copy link
Contributor

Choose a reason for hiding this comment

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

machine user 개념으로 CI/CD용 계정을 따로 만들어서 사용하는 건 어떨까요? Github Docs에도 나와있는 내용입니다!
참고 : https://docs.github.com/en/get-started/learning-about-github/types-of-github-accounts#user-accounts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

machine user 대신 최근 Github 추천하는 방식인 Github App을 사용해서 Repository에서 임시 토큰을 발급할 수 있도록 개선했습니다! 해당 문제는 해결된 것 같아요!


# 3. docker pull (전체 이미지 이름 사용)
echo "Pulling new image layer from GHCR..."
docker pull $FULL_IMAGE_NAME

# 4. 작업 디렉토리로 이동 및 Nginx 설정 이동
cd /home/${{ secrets.DEV_USERNAME }}/solid-connection-dev
mkdir -p ./nginx
mv ./nginx.dev.conf ./nginx/default.conf

# 5. Nginx 재시작
sudo cp ./nginx/default.conf /etc/nginx/conf.d/default.conf
sudo nginx -t
sudo nginx -s reload

cd /home/${{ secrets.DEV_USERNAME }}/solid-connection-dev

# 6. Docker Compose 재시작
echo "Restarting Docker Compose with tag: $IMAGE_TAG_ONLY"
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.dev.yml up -d --build
IMAGE_TAG=$IMAGE_TAG_ONLY docker compose -f docker-compose.dev.yml up -d

# 7. <none> 이미지 정리
echo "Pruning dangling docker images..."
docker image prune -f

# 8. stage 인스턴스의 오래된 태그 이미지 정리 (최신 5개 유지)
echo "Cleaning up old tagged images on host, keeping last 5..."
IMAGE_NAME_BASE="ghcr.io/${OWNER_LOWERCASE}/solid-connection-dev"

docker images "${IMAGE_NAME_BASE}" --format "{{.Tag}}" | \
sort -r | \
tail -n +6 | \
xargs -I {} docker rmi "${IMAGE_NAME_BASE}:{}" || true

echo "Deploy and Docker Compose restart finished."
'

# --- 6. 이미지 정리 ---
- name: Clean up old image versions from GHCR
if: success()
uses: snok/container-retention-policy@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
image-names: solid-connection-dev
delete-untagged: true
keep-n-tags: 5
account-type: org
org-name: ${{ github.repository_owner }}
cut-off: '7 days ago UTC'
Loading