diff --git a/.github/workflows/deploy.yml b/.github/workflows/basic_deploy.yml.disabled similarity index 90% rename from .github/workflows/deploy.yml rename to .github/workflows/basic_deploy.yml.disabled index f93c8b9..4652f3a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/basic_deploy.yml.disabled @@ -9,34 +9,46 @@ on: jobs: # ------------------------------------------------------------------- - # 1) CI: 컴파일·테스트·패키징만 + # 1) CI # ------------------------------------------------------------------- build: runs-on: ubuntu-latest steps: - # 코드 저장소에서 최신 커밋 가져오기 + # 1) 저장소 코드 가져오기 - name: Checkout code uses: actions/checkout@v3 + # 2) Java 21 설치 - name: Set up Java 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' - # Gradle을 통해 클린 빌드, 테스트, 패키징을수행 + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + # 4) Gradle로 빌드 - name: Run Gradle Build - run: ./gradlew clean build -x test --no-daemon + run: ./gradlew build -x test --no-daemon --build-cache # ------------------------------------------------------------------- - # 2) CD: EC2에 배포 (ci 성공 후 항상 실행) + # 2) CD: EC2에 배포 # ------------------------------------------------------------------- deploy: needs: build runs-on: ubuntu-latest steps: - # 1) 코드 체크아웃 + # 1) 저장소 코드 가져오기 - name: Checkout code uses: actions/checkout@v3 with: @@ -121,7 +133,7 @@ jobs: # depend_on에 의해 springboot가 가장 마지막 실행 for i in $(seq 1 $MAX_RETRIES); do HEALTH_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Health.Status}}' tokkit-springboot" 2>/dev/null || echo "none") - + if [ "$HEALTH_STATUS" = "healthy" ]; then echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" break @@ -129,14 +141,14 @@ jobs: echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" sleep $RETRY_INTERVAL fi - + if [ "$i" -eq "$MAX_RETRIES" ]; then echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." exit 1 fi done - - + + # 모든 컨테이너 상태 점검 COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") @@ -186,4 +198,4 @@ jobs: - name: Cleanup SSH if: always() run: rm -rf ~/.ssh - shell: bash + shell: bash \ No newline at end of file diff --git a/.github/workflows/docker_hub_deploy.yml b/.github/workflows/docker_hub_deploy.yml new file mode 100644 index 0000000..9161b3a --- /dev/null +++ b/.github/workflows/docker_hub_deploy.yml @@ -0,0 +1,227 @@ +name: Tokkit CI/CD Pipeline + +on: + push: + branches: + - develop + - main + workflow_dispatch: + +jobs: + # ------------------------------------------------------------------- + # 1) CI - 프로젝트 빌드 및 Docker Hub로 이미지 푸시 + # ------------------------------------------------------------------- + build: + runs-on: ubuntu-latest + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + + # 2) Java 21 설치 + - name: Set up Java 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + # 4) application.yml 생성 (secrets에서 불러오기) + - name: Generate Configuration Files + run: | + # application.yml 생성 + mkdir -p src/main/resources + cat > src/main/resources/application.yml << 'EOF' + ${{ secrets.APP_YML }} + EOF + + # 5) Gradle로 빌드 + - name: Run Gradle Build + run: ./gradlew clean build -x test --no-daemon --build-cache + + # 6) Docker Hub 로그인 + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # 7) Docker 이미지 빌드 및 푸시 + - name: Build and push Docker image + id: build_push + run: | + IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest + docker build \ + --file Dockerfile.cicd \ + --tag $IMAGE_TAG \ + . + docker push $IMAGE_TAG + echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT + + # ------------------------------------------------------------------- + # 2) CD: EC2 서버에 배포 + # ------------------------------------------------------------------- + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + # 1) SSH 키 설정 + - name: Setup SSH key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.EC2_SSH_KEY }} + + # 2) EC2에 접속해 코드 및 설정 파일 반영, Docker Compose로 배포 + - name: Deploy to EC2 + run: | + # 명확한 SSH 연결 정보 설정 + SSH_HOST="${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" + + # 원격 서버에 명령 실행 + ssh -o StrictHostKeyChecking=no $SSH_HOST <<'EOT' + set -e + cd ${{ secrets.APP_DIR }} + + echo "📥 Git pull" + git fetch origin + git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + + echo "📝 .env 생성" + cat > ./.env << 'EOF' + ${{ secrets.ENV_FILE }} + EOF + + echo "📝 application.yml 생성" + mkdir -p src/main/resources + cat > ./src/main/resources/application.yml << 'EOF' + ${{ secrets.APP_YML }} + EOF + + echo "🔒 Docker Hub 로그인" + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ + --username "${{ secrets.DOCKER_USERNAME }}" \ + --password-stdin + + # Docker Hub 사용자명 환경 변수 설정 + export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" + + echo "🔄 springboot 최신 이미지 가져오기" + docker pull ${DOCKER_USERNAME}/tokkit-springboot:latest + + echo "🚀 docker-compose로 서비스 실행" + docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up -d + EOT + + # 3) 컨테이너 헬스체크 및 배포 상태 확인 + - name: Check deployment status + run: | + # SSH 연결 정보 설정 + SSH_HOST="${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" + + # 헬스체크 루프 + # 헬스 체크 최대 10회(30초 간격) + MAX_RETRIES=10 + RETRY_INTERVAL=30 + + # depend_on에 의해 springboot가 가장 마지막 실행 + for i in $(seq 1 $MAX_RETRIES); do + HEALTH_STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo 'none'") + + if [ "$HEALTH_STATUS" = "healthy" ]; then + echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" + break + elif [ "$HEALTH_STATUS" = "none" ]; then + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: 컨테이너가 없거나 상태 확인 불가)" + + # 컨테이너 로그 확인 (컨테이너가 존재하는 경우) + CONTAINER_EXISTS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker ps -a --format '{{.Names}}' | grep tokkit-springboot || echo ''") + if [ ! -z "$CONTAINER_EXISTS" ]; then + echo "📋 tokkit-springboot 컨테이너 로그 (최근 30줄):" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs tokkit-springboot --tail 30" + fi + else + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" + fi + + sleep $RETRY_INTERVAL + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + + # 배포 상태 확인 + echo "📊 docker-compose 상태:" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml ps" + + # 스프링부트 컨테이너 로그 확인 + echo "📋 tokkit-springboot 컨테이너 로그 (최근 100줄):" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs tokkit-springboot --tail 100 || echo '로그를 가져올 수 없습니다.'" + + # 환경 변수 확인 + echo "🔍 환경 변수 확인:" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && grep DOCKER_USERNAME .env || echo 'DOCKER_USERNAME not found in .env'" + + exit 1 + fi + done + + # ✅ 모든 컨테이너 실행 상태 점검 + COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" + CONTAINERS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + UNHEALTHY_COUNT=0 + + echo "📊 컨테이너 상태 확인 중..." + for CONTAINER_ID in $CONTAINERS; do + # 각 컨테이너의 상태 확인 + CONTAINER_STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" + UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) + else + echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" + fi + done + + # 비정상 컨테이너 처리 + if [ "$UNHEALTHY_COUNT" -gt 0 ]; then + echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" + echo "=== 비정상 컨테이너 상세 상태 ===" + for CID in $CONTAINERS; do + STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Status}}' $CID") + if [ "$STATUS" != "running" ]; then + NAME=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + echo "🪫 $NAME 상태: $STATUS" + echo "--- $NAME 로그 (tail 50) ---" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs $NAME --tail=50" + fi + done + + # 상태 테이블 출력 + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + exit 1 + + # 성공 처리 + else + TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) + echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + fi + + # 4) SSH 키 제거 (보안 정리) + - name: Cleanup SSH + if: always() + run: rm -rf ~/.ssh + shell: bash \ No newline at end of file diff --git a/.github/workflows/jar_deploy.yml.disabled b/.github/workflows/jar_deploy.yml.disabled new file mode 100644 index 0000000..6cbb714 --- /dev/null +++ b/.github/workflows/jar_deploy.yml.disabled @@ -0,0 +1,237 @@ +name: Tokkit CI/CD Pipeline + +on: + push: + branches: + - develop + - main + workflow_dispatch: + +jobs: + # ------------------------------------------------------------------- + # 1) CI + # ------------------------------------------------------------------- + build: + runs-on: ubuntu-latest + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + + # 2) Java 21 설치 + - name: Set up Java 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + # 4) application.yml 생성 + - name: Generate application.yml from Secrets + run: | + mkdir -p src/main/resources + cat << 'EOF' > src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + # 5) Gradle로 빌드 + - name: Run Gradle Build + run: ./gradlew clean build -x test --no-daemon --build-cache + + # 6) JAR 업로드 + - name: Upload JAR artifact + uses: actions/upload-artifact@v4 + with: + name: springboot-jar + path: build/libs/tokkit.jar + + # ------------------------------------------------------------------- + # 2) CD: EC2에 배포 + # ------------------------------------------------------------------- + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # 2) JAR 다운로드 + - name: Download JAR artifact + uses: actions/download-artifact@v4 + with: + name: springboot-jar + path: build/libs + + # 3) .env 생성 및 확인 + - name: Create and verify env files + run: | + mkdir -p ./src/main/resources + + # 파일 생성 - 플레이스홀더 누락 문제 해결 + # application.yml 생성 + cat <<'EOF' > ./src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + + # .env 파일 생성 + cat <<'EOF' > ./.env + ${{ secrets.ENV_FILE }} + EOF + shell: bash + + + # 4) SSH 키 설정 (.ssh/config) + - name: Setup SSH key + env: + EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + run: | + mkdir -p ~/.ssh + echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + # SSH config 작성 + echo "Host ec2" > ~/.ssh/config + echo " HostName $EC2_HOST" >> ~/.ssh/config + echo " User $EC2_USER" >> ~/.ssh/config + echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config + echo " StrictHostKeyChecking no" >> ~/.ssh/config + shell: bash + + + # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 + # 수정하면서 application.yml 관련 코드는 필요없어짐 + - name: Update code and config on EC2 + run: | + # 1. EC2에서 git pull 먼저 + ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") + + if [ "$HEALTH_STATUS" = "healthy" ]; then + echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" + break + else + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" + sleep $RETRY_INTERVAL + fi + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + exit 1 + fi + done + + + # 모든 컨테이너 상태 점검 + COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" + CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + UNHEALTHY_COUNT=0 + + echo "📊 컨테이너 상태 확인 중..." + for CONTAINER_ID in $CONTAINERS; do + # 각 컨테이너의 상태 확인 + CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" + UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) + else + echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" + fi + done + + + # 비정상 컨테이너 처리 + if [ "$UNHEALTHY_COUNT" -gt 0 ]; then + echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" + echo "=== 비정상 컨테이너 상세 상태 ===" + for CID in $CONTAINERS; do + STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") + if [ "$STATUS" != "running" ]; then + NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + echo "🪫 $NAME 상태: $STATUS" + echo "--- $NAME 로그 (tail 50) ---" + ssh ec2 "docker logs $NAME --tail=50" + fi + done + + # 상태 테이블 출력 + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + exit 1 + + # 성공 처리 + else + TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) + echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + fi + # 8) SSH 설정 정리 + - name: Cleanup SSH + if: always() + run: rm -rf ~/.ssh + shell: bash \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0ca3536..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM gradle:8.4-jdk21 AS builder -WORKDIR /app - -COPY gradlew build.gradle settings.gradle ./ -COPY gradle gradle -RUN chmod +x gradlew - -COPY src src -RUN ./gradlew clean build -x test --no-daemon - -FROM openjdk:21-jdk-slim -WORKDIR /app - -# ✅ curl 설치 -RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /app/build/libs/Tokkit-Server-0.0.1-SNAPSHOT.jar app.jar -EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/Dockerfile.cicd b/Dockerfile.cicd new file mode 100644 index 0000000..f133708 --- /dev/null +++ b/Dockerfile.cicd @@ -0,0 +1,15 @@ +FROM openjdk:21-jdk-slim +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* + +# CI/CD에서 전송된 tokkit.jar만 복사 +COPY build/libs/tokkit.jar tokkit.jar + +# application.yml 복사 +#COPY src/main/resources/application.yml src/main/resources/application.yml + +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "tokkit.jar"] diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 0000000..96098bb --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,50 @@ +# --- 1) DEPENDENCY-CACHE --- +FROM gradle:8.4-jdk21 AS cache +WORKDIR /home/gradle/app + +# ① Gradle 캐시를 볼륨 아닌 일반 디렉토리에 저장 +ENV GRADLE_USER_HOME=/home/gradle/cache_home + +# ② Gradle 빌드 스크립트와 설정 파일 복사 +COPY ../gradlew build.gradle settings.gradle ./ +COPY ../gradle gradle/ + +# ③ 라이브러리 다운로드 +RUN chmod +x gradlew \ + && ./gradlew dependencies --no-daemon --build-cache + + +# --- 2) BUILD --- +FROM gradle:8.4-jdk21 AS builder +WORKDIR /home/gradle/app + +# ④ 캐시된 의존성 복사 (cache 스테이지 → builder) +COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle + +# ⑤ Gradle 빌드 스크립트와 설정 파일 복사 +COPY ../gradlew build.gradle settings.gradle ./ +COPY ../gradle gradle/ + +# ⑥ 실제 소스 코드 복사 +COPY ../src src/ + +# ⑦ 실제 빌드 +RUN chmod +x gradlew \ + && ./gradlew clean build -x test --no-daemon --build-cache + + +# --- 3) RUNTIME --- +FROM openjdk:21-jdk-slim +WORKDIR /app + +# ⑧ 런타임에 필요한 curl 설치 +RUN apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* + +# ⑨ 빌드 결과물 복사 + COPY --from=builder /home/gradle/app/build/libs/tokkit.jar tokkit.jar + + # ⑩ 실행 설정 + EXPOSE 8080 + ENTRYPOINT ["java", "-jar", "tokkit.jar"] diff --git a/build.gradle b/build.gradle index 1862362..1f2d093 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,18 @@ java { } } +jar { + archiveBaseName.set('tokkit') + archiveVersion.set('') // 버전 빼기 + archiveClassifier.set('plain') // 'plain' 접미사 붙이기 +} + +bootJar { + archiveBaseName.set("tokkit") + archiveVersion.set("") + archiveClassifier.set("") +} + configurations { compileOnly { extendsFrom annotationProcessor diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index c26029e..b618a4e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,20 +1,19 @@ services: - # SpringBoot + # springboot springboot: container_name: tokkit-springboot - build: - context: . # 현재 디렉토리 기준 Dockerfile 위치 - dockerfile: Dockerfile # Dockerfile - image: tokkit/springboot:latest + image: ${DOCKER_USERNAME}/tokkit-springboot:latest +# build: +# context: . # 현재 디렉토리 기준 Dockerfile 위치 +## dockerfile: Dockerfile.local # 수동 배포 시 사용하는 Dockerfile +# dockerfile: Dockerfile.cicd +# image: tokkit/springboot:latest ports: - "8080:8080" + env_file: + - .env environment: SPRING_PROFILES_ACTIVE: prod - SPRING_AI_OPENAI_API_KEY: ${SPRING_AI_OPENAI_API_KEY} - REDIS_PASSWORD: ${REDIS_PASSWORD} - RDS_USERNAME: ${RDS_USERNAME} - RDS_PASSWORD: ${RDS_PASSWORD} - CLOVA_OCR_SECRET_KEY: ${CLOVA_OCR_SECRET_KEY} depends_on: # 서비스 의존성 설정 redis: condition: service_healthy # redis가 정상 작동할 때만 시작