# 배포 매뉴얼

## 버전 등 정리

### 프론트엔드

- Vite (React + Typescript + Eslint)
- Zustand
- Axios
- Tanstack Query
- React Three Fiber(R3F)

### 백엔드

- Java
    - jdk 21
    - Zulu 21.0.3+9
        - [<https://www.azul.com/downloads/?package=jdk#zulu>](<https://www.azul.com/downloads/?package=jdk#zulu>)
    
- SpringBoot
    - 3.3.1
- JPA
    - 3.3.1
- Gradle
    - 8.9
- Redis
- Mysql

## 배포
모든 컴포넌트는 도커 컨테이너로 관리합니다.

### 젠킨스파일
gitlab의 파일을 가져와서 빌드하는 스크립트이다. Dockerfile을 참고하여 도커 이미지를 생성하고 컨테이너로 실행한다.

pipeline { agent any

environment {
    BACKEND_IMAGE = 'backend'
    FRONTEND_IMAGE = 'frontend'
    VITE_APP_BASE_URL = '<https://j11a206.p.ssafy.io/api/>'
    VITE_APP_SOCKET_URL = 'wss://j11a206.p.ssafy.io/omg'
}

stages {
    stage('Backend Build') {
        steps {
            script {
                echo '********** Backend Build Start **********'
                dir('omg-back') {
                    sh 'docker build -t docker-image/$BACKEND_IMAGE .'
                }
                echo '********** Backend Build End **********'
            }
        }
    }

    stage('Frontend Build') {
        steps {
            script {
                echo '********** Frontend Build Start **********'
                dir('omg-front') {
                    sh 'pwd'
                    sh 'ls -al'

                    sh """
                        docker build --no-cache \\
                        --build-arg VITE_APP_BASE_URL=${VITE_APP_BASE_URL} \\
                        --build-arg VITE_APP_SOCKET_URL=${VITE_APP_SOCKET_URL} \\
                        -t docker-image/$FRONTEND_IMAGE .
                    """
                }
                echo '********** Frontend Build End **********'
            }
        }
    }

    stage('Docker Compose Up') {
        steps {
            script {
                echo '******** Docker Compose Start ************'
                sh 'docker compose down'
                sh 'docker rm -f frontend || true'  // 이미 존재하는 컨테이너가 있다면 강제로 삭제
                sh 'docker rm -f backend || true'
                sh 'docker compose up -d'
                echo '********** Docker Compose End ***********'
            }
        }
    }
}

post {
    success {
        script {
            def Author_ID = sh(script: "git show -s --pretty=%an", returnStdout: true).trim()
            def Author_Name = sh(script: "git show -s --pretty=%ae", returnStdout: true).trim()
            mattermostSend (color: 'good',
            message: "빌드 성공: ${env.JOB_NAME} #${env.BUILD_NUMBER} by ${Author_ID}(${Author_Name})\\n(<${env.BUILD_URL}|Details>)",
            endpoint: '<https://meeting.ssafy.com/hooks/ceutz8ibfbgm3mwbji6f7yeehy>',
            channel: 'jenkins'
            )
        }
    }
    failure {
        script {
            def Author_ID = sh(script: "git show -s --pretty=%an", returnStdout: true).trim()
            def Author_Name = sh(script: "git show -s --pretty=%ae", returnStdout: true).trim()
            mattermostSend (color: 'danger',
            message: "빌드 실패: ${env.JOB_NAME} #${env.BUILD_NUMBER} by ${Author_ID}(${Author_Name})\\n(<${env.BUILD_URL}|Details>)",
            endpoint: '<https://meeting.ssafy.com/hooks/ceutz8ibfbgm3mwbji6f7yeehy>',
            channel: 'jenkins'
            )
        }
    }
}

}

### 프론트 관련 파일
여기에서는 프론트 Dockerfile, nginx 설정이 포함된다.

FROM node:lts-slim AS build

WORKDIR /app

COPY package.json . COPY package-lock.json .

RUN npm i

ARG VITE_APP_BASE_URL ARG VITE_APP_SOCKET_URL

ENV VITE_APP_BASE_URL=${VITE_APP_BASE_URL} ENV VITE_APP_SOCKET_URL=${VITE_APP_SOCKET_URL}

COPY . /app

RUN npm run build

FROM nginx:1.21.4-alpine

COPY nginx.conf /etc/nginx/conf.d/default.conf

COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80 CMD [ "nginx", "-g", "daemon off;" ]

nginx 설정

server { listen 80; server_name j11a206.p.ssafy.io; return 301 https://$host$request_uri;
}

server { listen 443 ssl; server_name j11a206.p.ssafy.io;

ssl_certificate /etc/nginx/ssl/live/j11a206.p.ssafy.io/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/j11a206.p.ssafy.io/privkey.pem;
ssl_trusted_certificate /etc/nginx/ssl/live/j11a206.p.ssafy.io/chain.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";

root /usr/share/nginx/html;
index index.html;

location /api/ {
    proxy_pass <http://backend: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;
}

location /omg {
    proxy_pass <http://backend:8080>;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    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;
}

location / {
    try_files $uri $uri/ /index.html;
}

location /models/ {
    autoindex on;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

error_page 404 /404.html;
location = /404.html {
    internal;
}

}

### 백엔드 관련 파일
Dockerfile과 Redis, Mysql 등 백엔드 관련 docker-compose.yml을 포함한다.

FROM gradle:8.8-jdk-focal AS build

WORKDIR /app

COPY build.gradle settings.gradle ./

RUN apt-get update && apt-get install -y openjdk-21-jdk

COPY . /app

RUN gradle clean build --no-daemon -x test

FROM openjdk:21-jdk-slim

WORKDIR /app

COPY --from=build /app/build/libs/*.jar /app/omg-backend.jar

EXPOSE 8080

ENTRYPOINT [ "java" ] CMD [ "-jar", "omg-backend.jar", "--spring.profiles.active=prod"]

mysql

version: '3'

services: mysql: image: mysql:8.0 container_name: mysql environment: MYSQL_ROOT_PASSWORD: ssafy MYSQL_DATABASE: omg
MYSQL_USER: ssafy
MYSQL_PASSWORD: ssafy
ports: - "3306:3306"
volumes: - /home/ubuntu/mysql/mysql_data:/var/lib/mysql - /home/ubuntu/mysql/mysql_data/my.cnf:/etc/mysql/my.cnf networks: - mysql_my_network restart: always

networks: mysql_my_network: external: true

redis

version: '3'

services: redis: image: "redis:latest" container_name: "redis" ports: - "6379:6379" networks: - mysql_my_network volumes: - redis-data:/data

networks: mysql_my_network: external: true

volumes: redis-data:

### 백엔드, 프론트엔드의 docker-compose.yml 
```json
version: '3'

services:
  spring_app:
    image: docker-image/backend:latest
    container_name: backend
    environment:
      DB_URL_PROD: jdbc:mysql://mysql:3306/omg
      DB_USERNAME_PROD: ssafy 
      DB_PASSWORD_PROD: ssafy
      REDIS_URL_PROD: redis
      REDIS_PORT_PROD: 6379
    ports:
      - "8080:8080"
    networks:
      - mysql_my_network
    volumes:
      - /home/ubuntu/logs:/app/logs
    restart: always

  frontend:
    image: docker-image/frontend:latest
    container_name: frontend
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mysql_my_network
    volumes:
      - /home/ubuntu/certbot/conf:/etc/nginx/ssl
      - /home/ubuntu/certbot/data:/var/www/certbot
    restart: always

networks:
  mysql_my_network:
    external: true