Repository: smwu-aws-cloud-club/courseX-project
Pull request #40: [Feat/#37] main 브랜치에 대한 CD 플로우 구현
Author: login: esc-beep
Labels: name: 🌱 feat
Created: 2025-05-03T02:59:10Z | Merged: 2025-05-10T13:03:06Z

# 📄 Work Description
main 브랜치에 머지될 때 Docker로 EC2에 배포될 수 있도록 CD 플로우를 구현했습니다.

# ⚙️ ISSUE
- closed #37 

# 💬 To Reviewers
제가 Springboot는 CORS 에러를 어디서 해결하는지 몰라 건드리지 못했습니다. 해당 부분만 수정하면 프론트엔드와도 바로 연결이 될 것 같습니다.


Linked issue #37: [FEAT] CD 플로우 구현
## ⚙️ ISSUE
- 배포를 위한 CD 플로우를 구현합니다.
- 프론트엔드는 Cloudflare에, 백엔드는 EC2에 Docker로 배포합니다.


## 📄 To-Do
- [x] 프론트엔드 Cloudflare 배포
- [ ] 백엔드 EC2 배포

Diff:
diff --git a/.github/workflows/main-CD.yml b/.github/workflows/main-CD.yml
new file mode 100644
index 0000000..80043e6
--- /dev/null
+++ b/.github/workflows/main-CD.yml
@@ -0,0 +1,91 @@
+name: courseX main CD
+
+on:
+  push:
+    branches: ['main']
+    paths:
+      - 'courseX-backend/**'
+      - 'docker/**'
+
+jobs:
+  deploy_backend:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v2
+        with:
+          distribution: 'temurin'
+          java-version: '17'
+
+      - name: Create resources folder and add env file
+        run: |
+          mkdir -p courseX-backend/src/main/resources/env
+          echo "${{ secrets.ENV_FILE }}" > courseX-backend/src/main/resources/env/prod.env
+
+      - name: Build backend
+        run: |
+          cd courseX-backend
+          chmod +x gradlew
+          ./gradlew clean build
+
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: ${{ secrets.AWS_REGION }}
+
+      - name: Login to Amazon ECR
+        id: login-ecr
+        uses: aws-actions/amazon-ecr-login@v1
+
+      - name: Build, tag, and push image to Amazon ECR
+        env:
+          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+          ECR_REPOSITORY: coursex-backend
+          IMAGE_TAG: ${{ github.sha }}
+        run: |
+          cd courseX-backend
+          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
+          docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
+
+      - name: Configure deployment files
+        run: |
+          export ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}
+          export IMAGE_TAG=${{ github.sha }}
+          export PREVIOUS_TAG=$(curl -s ${{ secrets.PREVIOUS_TAG_URL }} || echo "latest")
+          envsubst < docker/docker-compose.yml > docker/docker-compose.processed.yml
+          envsubst < docker/scripts/deploy.sh > docker/scripts/deploy.processed.sh
+
+      - name: Copy deployment files to EC2
+        uses: appleboy/scp-action@master
+        with:
+          host: ${{ secrets.EC2_HOST }}
+          username: ${{ secrets.EC2_USER }}
+          key: ${{ secrets.EC2_SSH_KEY }}
+          source: "docker/docker-compose.processed.yml,docker/scripts/deploy.processed.sh,docker/nginx/"
+          target: "/home/${{ secrets.EC2_USER }}/coursex-deploy"
+          strip_components: 1
+
+      - name: Deploy to Backend EC2
+        uses: appleboy/ssh-action@master
+        with:
+          host: ${{ secrets.EC2_HOST }}
+          username: ${{ secrets.EC2_USER }}
+          key: ${{ secrets.EC2_SSH_KEY }}
+          script: |
+            aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}
+            cd ~/coursex-deploy
+            mv docker-compose.processed.yml docker-compose.yml
+            mv deploy.processed.sh deploy.sh
+            chmod +x deploy.sh
+            echo "${{ secrets.ENV_FILE }}" > .env
+            export ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}
+            export IMAGE_TAG=${{ github.sha }}
+            ./deploy.sh
+            echo "${{ github.sha }}" > ~/previous-tag.txt
\ No newline at end of file
diff --git a/Docker/docker-compose.yml b/Docker/docker-compose.yml
new file mode 100644
index 0000000..aad108f
--- /dev/null
+++ b/Docker/docker-compose.yml
@@ -0,0 +1,42 @@
+version: '3'
+
+services:
+  backend-blue:
+    image: ${ECR_REGISTRY}/coursex-backend:${IMAGE_TAG:-latest}
+    container_name: coursex-backend-blue
+    restart: always
+    expose:
+      - "8080"
+    env_file:
+      - .env
+    environment:
+      - SPRING_PROFILES_ACTIVE=prod
+    volumes:
+      - ./logs:/app/logs-blue
+
+  backend-green:
+    image: ${ECR_REGISTRY}/coursex-backend:${PREVIOUS_TAG:-latest}
+    container_name: coursex-backend-green
+    restart: always
+    expose:
+      - "8080"
+    env_file:
+      - .env
+    environment:
+      - SPRING_PROFILES_ACTIVE=prod
+    volumes:
+      - ./logs:/app/logs-green
+
+  nginx:
+    image: nginx:latest
+    container_name: coursex-nginx
+    restart: always
+    ports:
+      - "80:80"
+      - "443:443"
+    volumes:
+      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
+      - ./nginx/conf.d:/etc/nginx/conf.d
+    depends_on:
+      - backend-blue
+      - backend-green
\ No newline at end of file
diff --git a/Docker/nginx/conf.d/default.conf b/Docker/nginx/conf.d/default.conf
new file mode 100644
index 0000000..669f990
--- /dev/null
+++ b/Docker/nginx/conf.d/default.conf
@@ -0,0 +1,27 @@
+server {
+    listen 80;
+    server_name _;
+
+    add_header 'Access-Control-Allow-Origin' 'https://course-x.kro.kr/' always;
+    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
+    add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
+    add_header 'Access-Control-Allow-Credentials' 'true' always;
+
+    location / {
+        proxy_pass http://backend-blue:8080;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        if ($request_method = 'OPTIONS') {
+            add_header 'Access-Control-Allow-Origin' '*' always;
+            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
+            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
+            add_header 'Access-Control-Allow-Credentials' 'true' always;
+            add_header 'Content-Type' 'text/plain charset=UTF-8';
+            add_header 'Content-Length' 0;
+            return 204;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Docker/nginx/nginx.conf b/Docker/nginx/nginx.conf
new file mode 100644
index 0000000..4eea4ec
--- /dev/null
+++ b/Docker/nginx/nginx.conf
@@ -0,0 +1,29 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /var/log/nginx/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    include /etc/nginx/conf.d/*.conf;
+}
\ No newline at end of file
diff --git a/Docker/scripts/deploy.sh b/Docker/scripts/deploy.sh
new file mode 100644
index 0000000..4d56633
--- /dev/null
+++ b/Docker/scripts/deploy.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+if [ -z "$ECR_REGISTRY" ] || [ -z "$IMAGE_TAG" ]; then
+  echo "환경 변수가 올바르게 설정되지 않았습니다."
+  echo "ECR_REGISTRY와 IMAGE_TAG가 필요합니다."
+  exit 1
+fi
+
+if [ -f .env ]; then
+  ACTUATOR_PATH_VALUE=$(grep -m 1 "ACTUATOR_PATH=" .env | cut -d '=' -f2)
+  if [ -n "$ACTUATOR_PATH_VALUE" ]; then
+    ACTUATOR_PATH=$ACTUATOR_PATH_VALUE
+  else
+    ACTUATOR_PATH="/actuator"
+  fi
+else
+  ACTUATOR_PATH="/actuator"
+fi
+
+HEALTH_ENDPOINT="${ACTUATOR_PATH}/health"
+
+ACTIVE_SERVICE=$(grep -o 'backend-[a-z]*' nginx/conf.d/default.conf | head -1)
+
+if [ "$ACTIVE_SERVICE" == "backend-blue" ]; then
+  TARGET_SERVICE="backend-green"
+else
+  TARGET_SERVICE="backend-blue"
+fi
+
+echo "현재 활성 서비스: $ACTIVE_SERVICE, 배포 대상: $TARGET_SERVICE"
+echo "Actuator 경로: $ACTUATOR_PATH"
+echo "헬스체크 엔드포인트: http://$TARGET_SERVICE:8080$HEALTH_ENDPOINT"
+
+docker pull $ECR_REGISTRY/coursex-backend:$IMAGE_TAG
+
+docker-compose up -d --no-deps $TARGET_SERVICE
+
+echo "헬스 체크 시작..."
+for i in {1..10}; do
+  echo "시도 $i/10..."
+  sleep 3
+
+  CONTAINER_STATUS=$(docker inspect --format="{{.State.Running}}" coursex-$TARGET_SERVICE 2>/dev/null)
+
+  if [ "$CONTAINER_STATUS" == "true" ]; then
+    HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" http://$TARGET_SERVICE:8080/actuator/health 2>/dev/null)
+
+    if [ "$HEALTH_CHECK" == "200" ]; then
+      echo "새 서비스 정상 작동 확인. Nginx 설정 변경 중..."
+
+      sed -i "s/$ACTIVE_SERVICE/$TARGET_SERVICE/g" nginx/conf.d/default.conf
+      docker exec coursex-nginx nginx -s reload
+
+      echo "트래픽이 $TARGET_SERVICE로 전환되었습니다."
+
+      exit 0
+    else
+      echo "헬스 체크 실패: 상태 코드 $HEALTH_CHECK"
+    fi
+  else
+    echo "컨테이너가 실행 중이지 않습니다."
+  fi
+done
+
+echo "새 서비스 상태 확인 실패. 배포 중단."
+exit 1
\ No newline at end of file
diff --git a/courseX-backend/Dockerfile b/courseX-backend/Dockerfile
new file mode 100644
index 0000000..574cdf9
--- /dev/null
+++ b/courseX-backend/Dockerfile
@@ -0,0 +1,9 @@
+FROM openjdk:17-jdk-slim
+
+WORKDIR /app
+
+COPY build/libs/*.jar app.jar
+
+EXPOSE 8080
+
+ENTRYPOINT ["java", "-jar", "app.jar"]
\ No newline at end of file


Reviews:
user: login: sung-silver
body: docker-compose 파일도 깃허브에서 관리하고 배포 시에 scp로 전송하도록 로직을 짜도 좋을 것 같습니다!

그리고 구현하신 플로우에 대한 설명을 자세하게 적어주시면 좋을 것 같아요! 나중에 readme 작성할 때 내용을 같이 넣으면 좋을 것 같아서요

CORS 에러는 Spring Security에서 설정하는 방식이 있고, nginx를 붙여 nginx 설정 파일을 내에서 수정하는 방식이 있습니다!
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T13:01:45Z
user: login: esc-beep
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T09:25:19Z
user: login: esc-beep
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T09:25:47Z
user: login: sung-silver
body: 모니터링 설정에 따른 actuator만 변경하면 될 것 같습니다!
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T06:17:18Z
user: login: esc-beep
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T13:09:51Z
user: login: sung-silver
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-10T12:55:18Z

Review comments:
diff_hunk: @@ -0,0 +1,66 @@
+name: courseX main CD
+
+on:
+  push:
+    branches: ['main']
+    paths:
+      - 'courseX-backend/**'
+
+jobs:
+  deploy_backend:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v2
+        with:
+          distribution: 'temurin'
+          java-version: '17'
+
+      - name: Create resources folder and add env file
+        run: |
+          mkdir -p courseX-backend/src/main/resources/env
path: .github/workflows/main-CD.yml
user: login: sung-silver
body: ec2에서도 env를 설정해주는 코드가 있을까요?? 그리고 docker-compose.yml도 깃허브에서 관리하고 scp로 전송하도록 구성하면 더 좋을 것 같다는 생각이 듭니다!!
created_at: 2025-05-03T12:59:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+name: courseX main CD
+
+on:
+  push:
+    branches: ['main']
+    paths:
+      - 'courseX-backend/**'
+
+jobs:
+  deploy_backend:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v2
+        with:
+          distribution: 'temurin'
+          java-version: '17'
+
+      - name: Create resources folder and add env file
+        run: |
+          mkdir -p courseX-backend/src/main/resources/env
+          echo "${{ secrets.ENV_FILE }}" > courseX-backend/src/main/resources/env/prod.env
+
+      - name: Build backend
+        run: |
+          cd courseX-backend
+          chmod +x gradlew
+          ./gradlew clean build
+
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: ${{ secrets.AWS_REGION }}
+
+      - name: Login to Amazon ECR
+        id: login-ecr
+        uses: aws-actions/amazon-ecr-login@v1
+
+      - name: Build, tag, and push image to Amazon ECR
+        env:
+          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+          ECR_REPOSITORY: coursex-backend
+          IMAGE_TAG: ${{ github.sha }}
+        run: |
+          cd courseX-backend
+          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
+          docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
+
+      - name: Deploy to Backend EC2
+        uses: appleboy/ssh-action@master
+        with:
+          host: ${{ secrets.BACKEND_EC2_HOST }}
+          username: ${{ secrets.EC2_USER }}
+          key: ${{ secrets.EC2_SSH_KEY }}
+          script: |
+            aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}
+            docker pull ${{ steps.login-ecr.outputs.registry }}/coursex-backend:latest
+            docker-compose -f ~/docker-compose.yml down
+            docker-compose -f ~/docker-compose.yml up -d
path: .github/workflows/main-CD.yml
user: login: sung-silver
body: 지금 무중단 배포는 아닌거죠?
created_at: 2025-05-03T13:00:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+name: courseX main CD
+
+on:
+  push:
+    branches: ['main']
+    paths:
+      - 'courseX-backend/**'
+
+jobs:
+  deploy_backend:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v2
+        with:
+          distribution: 'temurin'
+          java-version: '17'
+
+      - name: Create resources folder and add env file
+        run: |
+          mkdir -p courseX-backend/src/main/resources/env
+          echo "${{ secrets.ENV_FILE }}" > courseX-backend/src/main/resources/env/prod.env
+
+      - name: Build backend
+        run: |
+          cd courseX-backend
+          chmod +x gradlew
+          ./gradlew clean build
+
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: ${{ secrets.AWS_REGION }}
+
+      - name: Login to Amazon ECR
+        id: login-ecr
+        uses: aws-actions/amazon-ecr-login@v1
+
+      - name: Build, tag, and push image to Amazon ECR
+        env:
+          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+          ECR_REPOSITORY: coursex-backend
+          IMAGE_TAG: ${{ github.sha }}
+        run: |
+          cd courseX-backend
+          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
+          docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
+          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
+
+      - name: Deploy to Backend EC2
+        uses: appleboy/ssh-action@master
+        with:
+          host: ${{ secrets.BACKEND_EC2_HOST }}
+          username: ${{ secrets.EC2_USER }}
+          key: ${{ secrets.EC2_SSH_KEY }}
+          script: |
+            aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}
+            docker pull ${{ steps.login-ecr.outputs.registry }}/coursex-backend:latest
+            docker-compose -f ~/docker-compose.yml down
+            docker-compose -f ~/docker-compose.yml up -d
path: .github/workflows/main-CD.yml
user: login: esc-beep
body: 네! [feat: CD 워크플로우 개선 및 무중단 배포 설정](https://github.com/smwu-aws-cloud-club/courseX-project/pull/40/commits/a2f5441805c96e7452ec5ecaea82363992ec529f) 커밋에서 무중단 배포로 변경했습니다.
created_at: 2025-05-06T09:25:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+name: courseX main CD
+
+on:
+  push:
+    branches: ['main']
+    paths:
+      - 'courseX-backend/**'
+
+jobs:
+  deploy_backend:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v2
+        with:
+          distribution: 'temurin'
+          java-version: '17'
+
+      - name: Create resources folder and add env file
+        run: |
+          mkdir -p courseX-backend/src/main/resources/env
path: .github/workflows/main-CD.yml
user: login: esc-beep
body: 해당 내용 [feat: docker-compose 작성](https://github.com/smwu-aws-cloud-club/courseX-project/pull/40/commits/9c83db1833b725d45036d6db2b963e7e59cd0cfb)에 반영했습니다.
created_at: 2025-05-06T09:25:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+#!/bin/bash
+
+if [ -z "$ECR_REGISTRY" ] || [ -z "$IMAGE_TAG" ]; then
+  echo "환경 변수가 올바르게 설정되지 않았습니다."
+  echo "ECR_REGISTRY와 IMAGE_TAG가 필요합니다."
+  exit 1
+fi
+
+ACTIVE_SERVICE=$(grep -o 'backend-[a-z]*' nginx/conf.d/default.conf | head -1)
+
+if [ "$ACTIVE_SERVICE" == "backend-blue" ]; then
+  TARGET_SERVICE="backend-green"
+else
+  TARGET_SERVICE="backend-blue"
+fi
+
+echo "현재 활성 서비스: $ACTIVE_SERVICE, 배포 대상: $TARGET_SERVICE"
+
+docker pull $ECR_REGISTRY/coursex-backend:$IMAGE_TAG
+
+docker-compose up -d --no-deps $TARGET_SERVICE
+
+echo "헬스 체크 시작..."
+for i in {1..10}; do
+  echo "시도 $i/10..."
+  sleep 3
+
+  CONTAINER_STATUS=$(docker inspect --format="{{.State.Running}}" coursex-$TARGET_SERVICE 2>/dev/null)
+
+  if [ "$CONTAINER_STATUS" == "true" ]; then
+    HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" http://$TARGET_SERVICE:8080/actuator/health 2>/dev/null)
path: Docker/scripts/deploy.sh
user: login: sung-silver
body: 여기 actuator 경로도 환경변수화 되어 있어서 바꿔주어야 할 것 같습니다!
created_at: 2025-05-08T06:13:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+#!/bin/bash
+
+if [ -z "$ECR_REGISTRY" ] || [ -z "$IMAGE_TAG" ]; then
+  echo "환경 변수가 올바르게 설정되지 않았습니다."
+  echo "ECR_REGISTRY와 IMAGE_TAG가 필요합니다."
+  exit 1
+fi
+
+ACTIVE_SERVICE=$(grep -o 'backend-[a-z]*' nginx/conf.d/default.conf | head -1)
+
+if [ "$ACTIVE_SERVICE" == "backend-blue" ]; then
+  TARGET_SERVICE="backend-green"
+else
+  TARGET_SERVICE="backend-blue"
+fi
+
+echo "현재 활성 서비스: $ACTIVE_SERVICE, 배포 대상: $TARGET_SERVICE"
+
+docker pull $ECR_REGISTRY/coursex-backend:$IMAGE_TAG
+
+docker-compose up -d --no-deps $TARGET_SERVICE
+
+echo "헬스 체크 시작..."
+for i in {1..10}; do
+  echo "시도 $i/10..."
+  sleep 3
+
+  CONTAINER_STATUS=$(docker inspect --format="{{.State.Running}}" coursex-$TARGET_SERVICE 2>/dev/null)
+
+  if [ "$CONTAINER_STATUS" == "true" ]; then
+    HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" http://$TARGET_SERVICE:8080/actuator/health 2>/dev/null)
path: Docker/scripts/deploy.sh
user: login: esc-beep
body: [fix: actuator 경로를 환경변수와 일치하도록 수정](https://github.com/smwu-aws-cloud-club/courseX-project/pull/40/commits/f01e169c70f4c97cacf804c4a52ee6acf62622db) 커밋에서 수정 완료했습니다!
created_at: 2025-05-09T13:09:51Z
author_association: COLLABORATOR

PR comments:
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-05-06T09:23:10Z
body: ## Deploying coursex-project with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>f01e169</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://7f38feeb.coursex-project.pages.dev'>https://7f38feeb.coursex-project.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://feat--37.coursex-project.pages.dev'>https://feat--37.coursex-project.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/43f1f120608ba3b478fd8447ee7cff91/pages/view/coursex-project/7f38feeb-a452-4de5-a438-cc94f20925e1)

author_association: NONE
user: login: esc-beep
created_at: 2025-05-06T09:26:26Z
body: CORS 에러의 경우 [feat: Nginx 설정 및 CORS 처리 추가](https://github.com/smwu-aws-cloud-club/courseX-project/pull/40/commits/b9d5228eb1325b0cd49a7b1c7da70642d10c7030)에서 처리했습니다.
author_association: COLLABORATOR
user: login: esc-beep
created_at: 2025-05-06T09:27:37Z
body: CD 플로우는 다음과 같습니다.
1. main 브랜치에 코드가 병합되면 CD 워크플로우 시작
2. JDK 17 환경에서 Gradle을 사용해 백엔드 코드 빌드
3. 백엔드 코드를 Docker 이미지로 빌드
4. 빌드된 이미지를 AWS ECR에 업로드
5. docker-compose.yml 및 관련 설정 파일을 EC2 서버로 전송
6. Blue-Green 배포 방식을 통해 서비스 중단 없이 배포
author_association: COLLABORATOR