Skip to content
102 changes: 102 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: CD

on:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
name: Build & Push Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.push.outputs.digest }}

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=,format=short
type=raw,value=latest

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

- name: Build and push
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
name: Deploy to Server
runs-on: ubuntu-latest
needs: build-and-push
environment: production

steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
port: ${{ secrets.DEPLOY_PORT }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
envs: ENVFILE,GITHUB_TOKEN,ACTOR
script: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"

echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin

echo "$ENVFILE" > /home/${{ secrets.DEPLOY_USER }}/.aikon.env
chmod 600 /home/${{ secrets.DEPLOY_USER }}/.aikon.env

docker pull $IMAGE

docker stop aikon-app || true
docker rm aikon-app || true

docker run -d \
--name aikon-app \
--restart unless-stopped \
-p 8080:8080 \
--env-file /home/${{ secrets.DEPLOY_USER }}/.aikon.env \
$IMAGE

docker image prune -f
env:
ENVFILE: ${{ secrets.ENVFILE }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ACTOR: ${{ github.actor }}

41 changes: 40 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,36 @@ concurrency:
cancel-in-progress: true

jobs:
lint:
name: Lint (ktlint)
runs-on: ubuntu-latest

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

- name: Ensure Gradlew Executable
run: chmod +x ./gradlew

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false
cache-cleanup: always

- name: Run ktlintCheck
run: ./gradlew ktlintCheck --parallel --build-cache

build-and-test:
name: Build & Test
runs-on: ubuntu-latest
needs: lint

steps:
- name: Checkout
Expand All @@ -35,4 +63,15 @@ jobs:
cache-cleanup: always

- name: Build
run: ./gradlew build --parallel --build-cache
run: ./gradlew build -x test --parallel --build-cache

- name: Test
run: ./gradlew test --parallel --build-cache

- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report
path: build/reports/tests/test/
retention-days: 7
33 changes: 33 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# ---- Build Stage ----
FROM gradle:9.4.1-jdk21 AS builder

WORKDIR /app

COPY gradlew .

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

gradlew 파일이 복사될 때 실행 권한(+x)이 유실될 수 있습니다. 특히 Windows 환경에서 git 설정(core.filemode)에 따라 권한이 유지되지 않은 채 커밋된 경우, 빌드 단계에서 Permission denied 에러가 발생하며 빌드가 실패할 수 있습니다.\n\n안전한 빌드를 위해 gradlew 복사 후 실행 권한을 명시적으로 부여하는 것을 권장합니다.

COPY gradlew .
RUN chmod +x gradlew

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

17c2a41 에서 반영했습니다. (근거: Docker 공식 Best Practice — 실행 스크립트 파일은 권한 유실에 대비해 COPY 후 명시적으로 chmod +x 지정 권장)

RUN chmod +x gradlew
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .

# 의존성만 먼저 캐싱
RUN ./gradlew dependencies --no-daemon --parallel -q

COPY src src

RUN ./gradlew bootJar --no-daemon --parallel -x test && \
find build/libs -name "*.jar" ! -name "*-plain.jar" -exec mv {} build/libs/app.jar \;

# ---- Runtime Stage ----
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

RUN addgroup -S aikon && adduser -S aikon -G aikon

COPY --chown=aikon:aikon --from=builder /app/build/libs/app.jar app.jar

USER aikon

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]
Loading