GitLab CI/CD로 dev와 main 브랜치별 자동 배포 구성하기

짜장킴·2026년 6월 24일

실무

목록 보기
10/16
  • dev 브랜치 -> 테스트 서버 배포
  • main 브랜치 -> 운영 서버 배포

전체 파이프라인 구조

stages:

  • setup : 패키지 설치
  • test : 코드 검사
  • build : Next.js 빌드
  • deploy : 서버 배포

예시 파이프라인 구조

image: node:20-alpine

stages:
  - setup
  - test
  - build
  - deploy

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .npm/
    - node_modules/
    - .next/cache/

variables:
  SERVER_USER: "ubuntu"
  TEST_SERVER_HOST: "111.111.111.111"
  RELEASE_SERVER_HOST: "111.111.111.111"

install_job:
  stage: setup
  only:
    - dev
    - main
  script:
    - echo "패키지 설치를 시작합니다..."
    - npm ci --cache .npm --prefer-offline
    - echo "패키지 설치 완료!"

lint_test_job:
  stage: test
  only:
    - dev
    - main
  script:
    - echo "코드 스타일 및 타입을 검사합니다..."
    - npm run lint
    - echo "검사 통과!"

build_job:
  stage: build
  only:
    - dev
    - main
  script:
    - echo "[Client] 빌드를 시작합니다..."
    - rm -rf .next

    - echo "빌드용 환경변수 파일 생성 중..."
    - |
      if [ "$CI_COMMIT_BRANCH" = "dev" ]; then
        echo "NEXT_PUBLIC_API_BASE_URL=https://www.abc~" > .env.production
      elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
        echo "NEXT_PUBLIC_API_BASE_URL=https://www.bcd~" > .env.production
      else
        echo "지원하지 않는 브랜치입니다: $CI_COMMIT_BRANCH"
        exit 1
      fi

    - echo "NEXT_PUBLIC_NICE_CLIENT_KEY=${NEXT_PUBLIC_NICE_CLIENT_KEY}" >> .env.production

    - echo "생성된 .env.production 확인"
    - cat .env.production

    - npm run build

    - echo "파일 압축 중..."
    - tar -czf deploy.tar.gz .next public package.json package-lock.json next.config.ts .env.production

  artifacts:
    paths:
      - deploy.tar.gz
    expire_in: 1 hour

deploy_test_job:
  stage: deploy
  only:
    - dev

  variables:
    TARGET_DIR: "/home/ubuntu/"

  dependencies:
    - build_job

  before_script:
    - apk add --no-cache openssh-client sshpass

  script:
    - echo "테스트 서버로 전송 중 (${TEST_SERVER_HOST})..."
    - sshpass -p "$TEST_SERVER_PASSWORD" scp -o StrictHostKeyChecking=no deploy.tar.gz ${SERVER_USER}@${TEST_SERVER_HOST}:${TARGET_DIR}

    - echo "테스트 서버에서 압축 해제 및 배포 실행..."
    - |
      sshpass -p "$TEST_SERVER_PASSWORD" ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${TEST_SERVER_HOST} << EOF
        cd ${TARGET_DIR}

        echo "기존 파일 삭제 중..."
        rm -rf .next public

        echo "압축 해제 중..."
        tar -xzf deploy.tar.gz

        echo "압축 파일 삭제..."
        rm -f deploy.tar.gz

        echo "현재 서버 파일 목록:"
        ls -al

        echo "deploy.sh 확인:"
        ls -al deploy.sh || true

        echo "배포 스크립트 실행..."
        bash -l deploy.sh
      EOF

    - echo "[Client] 테스트 서버 배포가 완료되었습니다!"

deploy_release_job:
  stage: deploy
  only:
    - main

  variables:
    TARGET_DIR: "/home/ubuntu/"

  dependencies:
    - build_job

  before_script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - cp "$AWS_SSH_KEY" ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H "${RELEASE_SERVER_HOST}" >> ~/.ssh/known_hosts

  script:
    - echo "릴리즈 서버로 전송 중 (${RELEASE_SERVER_HOST})..."
    - scp -o StrictHostKeyChecking=no deploy.tar.gz ${SERVER_USER}@${RELEASE_SERVER_HOST}:${TARGET_DIR}

    - echo "릴리즈 서버에서 압축 해제 및 배포 실행..."
    - |
      ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${RELEASE_SERVER_HOST} << EOF
        cd ${TARGET_DIR}

        echo "기존 파일 삭제 중..."
        rm -rf .next public

        echo "압축 해제 중..."
        tar -xzf deploy.tar.gz

        echo "압축 파일 삭제..."
        rm -f deploy.tar.gz

        echo "현재 서버 파일 목록:"
        ls -al

        echo "deploy.sh 확인:"
        ls -al deploy.sh || true

        echo "배포 스크립트 실행..."
        bash -l deploy.sh
      EOF

    - echo "[Client] 릴리즈 서버 배포가 완료되었습니다!"

빌드 환경

  • image: node:20-alpine
  • GitLab Runner가 사용할 Node.js 환경 지정
  • node:20-alpine을 사용한 이유는 Node.js 20 버전을 사용할 수 있으면서도 이미지 크기가 작아 파이프라인 실행 속도에 유리하기 때문

캐시 설정

  • cache: key: ${CI_COMMIT_REF_SLUG}
    paths:
    - .npm/
    - node_modules/
    - .next/cache/
  • CI 환경은 매번 새로운 컨테이너에서 실행되기 때문에 패키지 설치와 빌드 캐시가 없으면 시간이 오래 걸리기 때문에 위의 항목들을 캐싱함
  • CI_COMMIT_REF_SLUG를 캐시 키로 사용하여 브랜치별로 캐시가 분리되도록 함

공통 변수 관리

  • 서버 접속에 필요한 공통 값을 변수로 분리함

1단계 : 패키지 설치

  • dev와 main 브랜치에서만 패키지 설치가 실행되도록 함
  • npm ci는 package-lock.json을 기준으로 정확하게 의존성을 설치하기 때문에 CI 환경에서 더 안정적

2단계 : 코드 검사

  • 빌드 전에 코드 스타일과 타입 검사 수행
  • 문제가 있는 코드가 서버에 배포되는 것을 막을 수 있음

3단계 : 브랜치별 환경변수 생성 후 빌드

  • dev와 main 브랜치에서만 빌드가 실행
  • rm -rf .next : 기존 빌드 파일 삭제
  • 브랜치별 .env.production 생성
  • Next.js 빌드 실행
  • tar -czf~ : 배포 파일 압축
    - 압축 대상 :
    .next
    public
    package.json
    package-lock.json
    next.config.ts
    .env.production
  • Artifacts로 배포 파일 전달

4단계 : 테스트 서버 배포

  • 테스트 서버는 비밀번호 기반 SSH 접속 사용
  • sshpass를 설치한 뒤 TEST_SERVER_PASSWORD를 사용해 접속함
  • scp deploy.tar.gz~ : 빌드 단게에서 만든 압축 파일을 테스트 서버로 전송
  • cd ${TARGET_DIR} : 배포 경로로 이동
  • rm -rf .next public : 기존 .next, public 삭제
  • tar -xzf deploy.tar.gz : 압축 파일 해제
  • rm -f deploy.tar.gz : 압축 파일 삭제
  • bash -l deploy.sh : deploy.sh 실행
  • deploy.sh에 삭제 해제 등의 명령어가 있다면 rm~ 따로 실행하지 않아도 됨

4단계 : 릴리즈 서버 배포

  • TARGET_DIR 따로 설정
  • SSH Key 방식으로 접속
  • GitLab Variables에 등록된 AWS_SSH_KEY를 Runner 내부의 ~/.ssh/id_rsa로 복사하고 권한을 설정
  • SSH 키 파일은 권한이 너무 열려 있으면 사용할 수 없기 때문에 600 권한을 지정해야 함
  • known_hosts 등록 : SSH 접속 시 호스트 확인 프롬프트가 뜨지 않도록 서버 정보를 미리 등록
  • 릴리즈 서버로 파일 전송
  • 테스트 서버와 동일하게 서버 내부에서 압축을 풀고 deploy.sh 실행

다른 프로젝트에서 base path가 필요한 경우

  • 테스트 서버는 하나의 도메인 아래 특정 경로로 프론트엔드를 서비스해야 했기 때문에 basePath가 필요했음
  • 반면 로컬과 운영 서버는 루트 경로(/)에서 서비스되므로 basePath를 적용하지 않아야 함
- |
  if [ "$CI_COMMIT_BRANCH" = "dev" ]; then
    echo "NEXT_PUBLIC_API_BASE_URL=https://www.abc~" > .env.production
    echo "NEXT_PUBLIC_BASE_PATH=/경로" >> .env.production
  elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
    echo "NEXT_PUBLIC_API_BASE_URL=https://www.bcd~" > .env.production
  else
    echo "지원하지 않는 브랜치입니다: $CI_COMMIT_BRANCH"
    exit 1
  fi

=> dev 브랜치에서만 .env.production에 NEXT_PUBLIC_BASE_PATH=/경로 를 추가함

  • 또한 next.config.ts 설정
import type { NextConfig } from "next";

const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";

const nextConfig: NextConfig = {
  basePath,
};

export default nextConfig;
  • 테스트 서버에서만 사용할거라면 .env.local에도 적지 않아야함
profile
프론트엔드

0개의 댓글