Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI (develop)

on:
pull_request:
branches: [ "develop" ]

permissions:
contents: read

jobs:
build-test:
runs-on: ubuntu-latest

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

- name: Set up JDK 17
uses: actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "17"
cache: gradle

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

- name: Build & Test (Gradle)
run: ./gradlew clean build
101 changes: 101 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: CI/CD (main)

on:
push:
branches: [ "main" ]

permissions:
contents: read

concurrency:
group: valuedi-prod-deploy
cancel-in-progress: true

jobs:
build-and-push:
runs-on: ubuntu-latest

outputs:
image_repo: ${{ steps.meta.outputs.image_repo }}
image_tag: ${{ steps.meta.outputs.image_tag }}

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

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

- name: Log in to Docker Hub
uses: docker/login-action@v3.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Set image meta
id: meta
run: |
echo "image_repo=${{ secrets.DOCKER_USERNAME }}/valuedi-backend" >> $GITHUB_OUTPUT
echo "image_tag=${{ github.sha }}" >> $GITHUB_OUTPUT

- name: Build & Push (latest + sha)
uses: docker/build-push-action@v6.16.0
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ steps.meta.outputs.image_repo }}:latest
${{ steps.meta.outputs.image_repo }}:${{ steps.meta.outputs.image_tag }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
runs-on: ubuntu-latest
needs: build-and-push

steps:
- name: Deploy via SSH (Blue/Green)
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.PROD_SERVER_HOST }}
port: ${{ secrets.PROD_SERVER_PORT }}
username: ${{ secrets.PROD_SERVER_USERNAME }}
key: ${{ secrets.PROD_SERVER_KEY }}
script_stop: true
script: |
set -euo pipefail

APP_DIR="/home/ubuntu/valuedi/app"
cd "$APP_DIR"

# 1) PROD_ENV를 .env로 반영 (멀티라인 안전)
cat > .env << 'EOF'
${{ secrets.PROD_ENV }}
EOF

# 2) IMAGE_REPO / IMAGE_TAG 강제 주입 (PROD_ENV에 없거나 달라도 덮어씀)
IMAGE_REPO="${{ needs.build-and-push.outputs.image_repo }}"
IMAGE_TAG="${{ needs.build-and-push.outputs.image_tag }}"

# IMAGE_REPO 업데이트/추가
if grep -q '^IMAGE_REPO=' .env; then
sed -i "s|^IMAGE_REPO=.*|IMAGE_REPO=${IMAGE_REPO}|" .env
else
echo "IMAGE_REPO=${IMAGE_REPO}" >> .env
fi

# IMAGE_TAG 업데이트/추가
if grep -q '^IMAGE_TAG=' .env; then
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${IMAGE_TAG}|" .env
else
echo "IMAGE_TAG=${IMAGE_TAG}" >> .env
fi

# 3) 최신 이미지 pull 확인 + 배포
chmod +x deploy.sh scripts/healthcheck.sh scripts/switch_upstream.sh
./deploy.sh

# 4) 사용하지 않는 이미지 정리(선택)
docker image prune -f
38 changes: 38 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1.7

# ---------- Build Stage ----------
FROM eclipse-temurin:17-jdk-jammy AS build
WORKDIR /workspace

# Gradle 캐시 최적화: 설정/래퍼를 먼저 복사
COPY gradlew .
COPY gradle gradle
COPY settings.gradle* build.gradle* gradle.properties* ./
# 멀티모듈/버전카탈로그 사용 시
COPY gradle/libs.versions.toml gradle/libs.versions.toml

# 의존성 캐시 (소스 없이도 가능한 단계)
RUN chmod +x gradlew \
&& ./gradlew --no-daemon dependencies > /dev/null 2>&1 || true

# 소스 복사
COPY src src

# 빌드 (테스트는 CI에서 수행하므로 컨테이너 빌드에서는 제외 권장)
RUN ./gradlew --no-daemon clean bootJar -x test

# ---------- Run Stage ----------
FROM eclipse-temurin:17-jre-jammy AS runtime
WORKDIR /app

# 보안/권장: non-root 유저로 실행
RUN useradd -ms /bin/bash appuser
USER appuser

# bootJar 복사
COPY --from=build /workspace/build/libs/*.jar app.jar

EXPOSE 8080

# 기본 JVM 옵션(필요 시 docker-compose/.env에서 JAVA_TOOL_OPTIONS로 덮어쓰기 가능)
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "app.jar"]
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
// Spring Boot Core
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// Lombok
compileOnly 'org.projectlombok:lombok'
Expand All @@ -43,6 +44,7 @@ dependencies {
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'com.h2database:h2'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
24 changes: 16 additions & 8 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ spring:

datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
url: ${DB_URL:jdbc:mysql://localhost:3306/valuedi?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8}
username: ${DB_USERNAME:test}
password: ${DB_PASSWORD:test}
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
show-sql: ${SHOW_SQL}
show-sql: ${SHOW_SQL:false}
hibernate:
ddl-auto: ${DDL_AUTO}
ddl-auto: ${DDL_AUTO:none}
properties:
hibernate:
format_sql: true
format_sql: true
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: never
30 changes: 30 additions & 0 deletions src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:valuedi_test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:

jpa:
database: h2
database-platform: org.hibernate.dialect.H2Dialect
show-sql: false
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true

data:
redis:
host: localhost
port: 6379

management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: never