๐Ÿ’ช์˜จํ•(on-fit) ์ฒซ CI/CD ๋ฐฐํฌ๊ธฐ 3ํŽธ

์กฐ์ค€ํ˜•ยท2025๋…„ 11์›” 18์ผ

์˜จํ•

๋ชฉ๋ก ๋ณด๊ธฐ
10/16

GitHub Actions๋กœ CI + CD ํŒŒ์ดํ”„๋ผ์ธ ์™„์„ฑํ•˜๊ธฐ

์•ž์˜ ๋‘ ํŽธ๊นŒ์ง€๋Š” ์ „๋ถ€ ์ˆ˜๋™ ๋ฐฐํฌ์˜€๋‹ค.

์ด์ œ๋ถ€ํ„ฐ๋Š” ์ง„์งœ ๊ฐœ๋ฐœ์ž๋‹ค์šด(?) CI/CD ์ด์•ผ๊ธฐ๋ฅผ ์ ์–ด๋ณด๋ ค ํ•œ๋‹ค.


1. ๋ชฉํ‘œ ํŒŒ์ดํ”„๋ผ์ธ

๋‚ด๊ฐ€ ์„ค๊ณ„ํ•œ ํ๋ฆ„์€ ์ด๋ ‡๋‹ค.

  1. dev ๋ธŒ๋žœ์น˜์—์„œ ๊ฐœ๋ฐœ
  2. dev์— push ๋  ๋•Œ๋งˆ๋‹ค โ†’ CI ๋นŒ๋“œ ์ฒดํฌ
  3. dev โ†’ main์œผ๋กœ PR ์ƒ์„ฑ
  4. PR์—์„œ ๋‹ค์‹œ CI ์ฒดํฌ ํ†ต๊ณผํ•ด์•ผ๋งŒ โ†’ main์œผ๋กœ Merge ๊ฐ€๋Šฅ
  5. main์— ๋จธ์ง€๋˜๋ฉด โ†’ ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ(deploy.yml) ์‹คํ–‰
  6. GitHub Actions๊ฐ€ EC2์— SSH๋กœ ์ ‘์†ํ•ด์„œ:
    • git fetch / reset
    • npm install --omit=dev
    • npm run build
    • pm2 restart onfit ์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰

์ฆ‰,

PR = CI (๊ฒ€์ฆ)

Merge = CD (๋ฐฐํฌ)

๊ฐ€ ๋˜๋„๋ก ๋งŒ๋“  ๊ฒƒ์ด๋‹ค.


2. CD: GitHub Actions โ†’ EC2 ์ž๋™ ๋ฐฐํฌ (deploy.yml)

2-1. GitHub Secrets ์„ค์ •

๋จผ์ € GitHub ๋ ˆํฌ ์„ค์ •์—์„œ Actions Secrets๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

  • Settings โ†’ Secrets and variables โ†’ Actions โ†’ New repository secret

๋‚ด๊ฐ€ ๋งŒ๋“  ์ฃผ์š” ์‹œํฌ๋ฆฟ๋“ค์€:

  • EC2_HOST : 35.74.238.231 (EC2 ํผ๋ธ”๋ฆญ IP)
  • EC2_KEY : onfit.pem ํŒŒ์ผ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๋ณต๋ถ™ํ•œ ๊ฐ’

EC2_KEY๋Š” ๋งฅ์—์„œ:

cat ~/Downloads/onfit.pem

๋กœ ๋‚ด์šฉ์„ ์ถœ๋ ฅํ•ด์„œ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌ + GitHub Secret์— ๋ถ™์—ฌ ๋„ฃ์—ˆ๋‹ค.

(์ด๋•Œ Keychain์ด ์—ด๋ฆฌ๋ ค๊ณ  ํ•  ๋•Œ๋งˆ๋‹ค ์ทจ์†Œํ•˜๊ณ , ๊ทธ๋ƒฅ ํ…์ŠคํŠธ๋กœ ๋ณต์‚ฌํ•˜๋Š” ๊ฒŒ ํฌ์ธํŠธ.)

2-2. ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ ์ƒ์„ฑ

๋ ˆํฌ ๋ฃจํŠธ์—์„œ:

.github/workflows/deploy.yml

์„ ๋งŒ๋“ค๊ณ  ์•„๋ž˜ ๋‚ด์šฉ์„ ์ž‘์„ฑํ–ˆ๋‹ค.

name: Deploy to EC2

on:
  push:
    branches:
      - main       # main์— push๋  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Deploy on EC2 via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_KEY }}
          script: |
            cd /home/ubuntu/on-fit/on-fit-next

            echo "[DEPLOY] git fetch & reset to origin/main..."
            git fetch origin
            git reset --hard origin/main

            echo "[DEPLOY] install dependencies..."
            npm install --omit=dev

            echo "[DEPLOY] build..."
            npm run build

            echo "[DEPLOY] restart pm2..."
            pm2 restart onfit || pm2 start npm --name "onfit" -- start

            echo "[DEPLOY] done."

์—ฌ๊ธฐ์„œ ๊ฒฝ๋กœ๋Š” ์‹ค์ œ EC2 ์•ˆ์—์„œ์˜ ํ”„๋กœ์ ํŠธ ์œ„์น˜์— ๋งž์ถฐ์•ผ ํ•œ๋‹ค.

/home/ubuntu/on-fit/on-fit-next

์ดˆ๊ธฐ์—๋Š” SSH ํƒ€์ž„์•„์›ƒ(i/o timeout) ์—๋Ÿฌ๋„ ๋‚ฌ์—ˆ๋Š”๋ฐ, ๊ทธ๊ฑด ๋ณด์•ˆ ๊ทธ๋ฃน์—์„œ 22๋ฒˆ ํฌํŠธ๋ฅผ GitHub Actions์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์—ด์–ด์ฃผ๋ฉด์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค.

์ด์ œ main์— ์–ด๋–ค ์ปค๋ฐ‹์ด ํ‘ธ์‹œ๋˜๋ฉด Actions ํƒญ์—์„œ ์ด๋ ‡๊ฒŒ ๋กœ๊ทธ๊ฐ€ ๋‚˜์˜จ๋‹ค.

  • git fetch/reset
  • npm install
  • npm run build
  • pm2 restart

๊ทธ ํ›„ https://onfit.today์— ์ ‘์†ํ•˜๋ฉด ๊ณง๋ฐ”๋กœ ์ตœ์‹  ์ฝ”๋“œ๊ฐ€ ๋ฐ˜์˜๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


3. CI: dev / PR ๋‹จ๊ณ„์—์„œ ๋นŒ๋“œ ์ฒดํฌ (ci.yml)

CD๊นŒ์ง€ ๊ตฌ์ถ•ํ•˜๊ณ  ๋‚˜๋‹ˆ, ํ•œ ๊ฐ€์ง€ ๊ฑฑ์ •์ด ๋‚จ์•˜๋‹ค.

โ€œ๋งŒ์•ฝ ๋นŒ๋“œ๊ฐ€ ๊นจ์ง€๋Š” ์ฝ”๋“œ๋ฅผ main์— mergeํ•˜๋ฉด?

EC2 ๋ฐฐํฌ ๋‹จ๊ณ„์—์„œ ๋ฐ”๋กœ ํ„ฐ์ง€๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€โ€ฆ?โ€

๊ทธ๋ž˜์„œ main์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ๋ฌด์กฐ๊ฑด ๋นŒ๋“œ๊ฐ€ ์„ฑ๊ณตํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” CI๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

3-1. CI ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ ์ƒ์„ฑ

on-fit-next ํ”„๋กœ์ ํŠธ๊ฐ€ on-fit ๋ ˆํฌ ์•ˆ์ชฝ์— ์žˆ์œผ๋ฏ€๋กœ,

working directory๋ฅผ ./on-fit-next๋กœ ์ง€์ •ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ–ˆ๋‹ค.

.github/workflows/ci.yml

name: CI

on:
  pull_request:
    branches: [ main ]   # dev -> main PR
  push:
    branches: [ dev ]    # dev์— pushํ•  ๋•Œ๋„ ์ฒดํฌ

jobs:
  build:
    runs-on: ubuntu-latest

    env:
      NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
      NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
      SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        working-directory: ./on-fit-next
        run: npm install

      - name: Build project
        working-directory: ./on-fit-next
        run: npm run build

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ํฌ์ธํŠธ๋Š” ๋‘ ๊ฐ€์ง€์˜€๋‹ค.

  1. working-directory ๋ฅผ ๊ผญ ./on-fit-next๋กœ ์„ค์ •

    โ†’ ๋ฃจํŠธ(on-fit)์—๋Š” package.json์ด ์—†์–ด์„œ, ์ฒ˜์Œ์—” ENOENT ์—๋Ÿฌ๊ฐ€ ๋‚ฌ์—ˆ๋‹ค.

  2. Supabase ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ฅผ secrets์— ๋„ฃ๊ณ  env๋กœ ์ฃผ์ž…

    โ†’ CI ํ™˜๊ฒฝ์€ .env ํŒŒ์ผ์ด ์—†์–ด์„œ, supabaseUrl is required ์—๋Ÿฌ๊ฐ€ ๋–ด๋‹ค.

    โ†’ NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY ๋“ฑ์„ Secrets์— ๋„ฃ๊ณ  env๋กœ ๋„˜๊ธฐ๋‹ˆ ํ•ด๊ฒฐ.


4. CI๊ฐ€ ์žก์•„์ค€ ์‹ค์ œ ๋ฒ„๊ทธ๋“ค

CI๋ฅผ ๋Œ๋ฆฌ๋ฉด์„œ ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ์‚ฌ์ „์— ์žก์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

4-1. ๋ชจ๋“ˆ ๋ˆ„๋ฝ: @radix-ui/react-dialog

Dialog ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ @radix-ui/react-dialog๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ,

๋กœ์ปฌ์—” ์„ค์น˜๋˜์–ด ์žˆ์ง€๋งŒ package.json์— ๋ฐ˜์˜์ด ์•ˆ ๋ผ ์žˆ์–ด์„œ CI์—์„œ ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹ค.

Module not found: Can't resolve '@radix-ui/react-dialog'

ํ•ด๊ฒฐ:

cd on-fit-next
npm install @radix-ui/react-dialog
git add package.json package-lock.json
git commit -m "fix: add radix dialog dependency"

4-2. TypeScript strict ์—๋Ÿฌ

์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋Ÿฐ ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ์žˆ์—ˆ๋‹ค.

Type error: Type 'string | undefined' is not assignable to type 'string'.

PostInfo โ†’ ReportModal๋กœ ๋„˜๊ธฐ๋Š” props์—์„œ:

<ReportModal
  ...
  postTitle={data?.title ?? ""}
  targetUserId={data?.profile?.id}
/>

targetUserId๊ฐ€ string | undefined๋ผ์„œ ์—๋Ÿฌ.

CI๊ฐ€ ์—†๋‹ค๋ฉด ์‹ค์ œ ๋ฐฐํฌํ•  ๋•Œ๊นŒ์ง€ ๋ชฐ๋ž์„ ์ด์Šˆ์˜€๋‹ค.

์ˆ˜์ •:

<ReportModal
  ...
  postTitle={data?.title ?? ""}
  targetUserId={data?.profile?.id ?? ""}
/>

๋˜ํ•œ useSearchParams๋ฅผ ์‚ฌ์šฉํ•˜๋Š” /auth ํŽ˜์ด์ง€์—์„œ

Next.js 16์˜ โ€œCSR bail-out + Suspenseโ€ ๊ด€๋ จ ๊ฒฝ๊ณ /์—๋Ÿฌ๋„ ์žก์œผ๋ฉด์„œ,

์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง๊นŒ์ง€ ์ง„ํ–‰ํ–ˆ๋‹ค.


5. ๋ธŒ๋žœ์น˜ ๋ณดํ˜ธ ๊ทœ์น™ (main์— ๊นจ์ง„ ์ฝ”๋“œ ๋ชป ๋“ค์–ด์˜ค๊ฒŒ ๋ง‰๊ธฐ)

CI๊นŒ์ง€ ๋งŒ๋“ค๊ณ  ๋‚˜์„œ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•œ ๋‹จ๊ณ„ ๋”:

โ€œCI๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์•„์˜ˆ main์— ๋จธ์ง€ ์ž์ฒด๊ฐ€ ์•ˆ ๋˜๊ฒŒ ๋งŒ๋“ค์ž.โ€

GitHub์—์„œ:

  1. Settings โ†’ Branches
  2. main์— ๋Œ€ํ•ด Add branch protection rule
  3. ์˜ต์…˜ ์„ค์ •:
    • Branch name pattern: main
    • โœ… Require status checks to pass before merging
    • ์•„๋ž˜ ๋ฆฌ์ŠคํŠธ์—์„œ CI ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด:

  • dev โ†’ main PR ์ƒ์„ฑ
  • CI๊ฐ€ ๋Œ์•„์„œ ์ดˆ๋ก๋ถˆ์ด ์•„๋‹ˆ๋ฉด
  • Merge ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋œ๋‹ค.

์ฆ‰, โ€œ๋นŒ๋“œ ์•ˆ ๋˜๋Š” ์ฝ”๋“œ๋Š” main์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์—†๋‹คโ€๋Š” ๊ทœ์น™์ด ๊ฐ•์ œ๋œ๋‹ค.


6. ์ตœ์ข… ๊ฐœ๋ฐœ ํ”Œ๋กœ์šฐ ์ •๋ฆฌ

์ด์ œ ์˜จํ•์˜ ๊ธฐ๋ณธ ๊ฐœ๋ฐœ/๋ฐฐํฌ ํ”Œ๋กœ์šฐ๋Š” ์ด๋ ‡๊ฒŒ ๋Œ์•„๊ฐ„๋‹ค.

  1. ๊ฐœ๋ฐœ์ž๋Š” dev ๋ธŒ๋žœ์น˜์—์„œ ์ž‘์—…

    • ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • npm run build ๋กœ์ปฌ ํ™•์ธ
  2. dev์— push

    โ†’ GitHub Actions CI๊ฐ€ ์ž๋™์œผ๋กœ ๋นŒ๋“œ ๋Œ๋ฆผ

  3. dev โ†’ main PR ์ƒ์„ฑ

    • PR์—์„œ ๋‹ค์‹œ CI ์‹คํ–‰
    • ์ดˆ๋ก๋ถˆ์ด ๋˜์–ด์•ผ Merge ๊ฐ€๋Šฅ
  4. Merge pull request ๋ฒ„ํŠผ ํด๋ฆญ

    • main์— ์ปค๋ฐ‹์ด ์Œ“์ž„
    • ๋™์‹œ์— Deploy to EC2 ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰
  5. ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ (CD) ์ˆ˜ํ–‰

    • EC2 SSH ์ ‘์†
    • git reset --hard origin/main
    • npm install --omit=dev
    • npm run build
    • pm2 restart onfit
  6. ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ €

    • https://onfit.today ์ ‘์†
    • ํ•ญ์ƒ ์ตœ์‹  main ์ฝ”๋“œ๊ฐ€ ๋Œ์•„๊ฐ

๋งˆ๋ฌด๋ฆฌ โ€“ ์ฒซ CI/CD ๋ฐฐํฌ๋ฅผ ํ•˜๋ฉด์„œ ๋А๋‚€ ์ 

์ด๋ฒˆ ์˜จํ• ํ”„๋กœ์ ํŠธ ๋ฐฐํฌ๋ฅผ ์ค€๋น„ํ•˜๋ฉด์„œ:

  • ๋งจ๋•…์— ํ—ค๋”ฉ์œผ๋กœ EC2์— Next.js ์˜ฌ๋ ค๋ณด๊ธฐ
  • Nginx + ๋„๋ฉ”์ธ + HTTPS๊นŒ์ง€ ์†์œผ๋กœ ์—ฎ์–ด๋ณด๊ธฐ
  • ๊ทธ๋ฆฌ๊ณ  GitHub Actions๋กœ โ€œCI โ†’ Merge โ†’ CDโ€ ์ž๋™ํ™”๊นŒ์ง€ ์—ฐ๊ฒฐ

์ด๋ผ๋Š” ์ „์ฒด ํ๋ฆ„์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์ง์ ‘ ๊ฒช์–ด๋ดค๋‹ค.

๊ฐ€์žฅ ํฌ๊ฒŒ ๋А๋‚€ ๋‘ ๊ฐ€์ง€๋Š”:

  1. CI๊ฐ€ ์—†์œผ๋ฉด โ€œ๋‚ด ๋กœ์ปฌ์—์„œ๋Š” ๋˜๋Š”๋ฐโ€ฆโ€๋ฅผ ์˜์›ํžˆ ๋ฐ˜๋ณตํ•  ์ˆ˜ ์žˆ๋‹ค
    • ๋ชจ๋“ˆ ๋ˆ„๋ฝ, TypeScript ์—๋Ÿฌ, ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ˆ„๋ฝ ๋“ฑ
    • CI๊ฐ€ ์•„๋‹ˆ์—ˆ์œผ๋ฉด ์ „๋ถ€ ์„œ๋ฒ„์—์„œ๋งŒ ํ„ฐ์กŒ์„ ๋ฌธ์ œ๋“ค์ด๋‹ค.
  2. CD๋ฅผ ๋ถ™์—ฌ๋†”์•ผ โ€˜๋ฐฐํฌ๊ฐ€ ๊ท€์ฐฎ๋‹คโ€™๋Š” ์ด์œ ๋กœ main๋ฅผ ๋ฐฉ์น˜ํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค
    • ๋‹จ์ˆœํžˆ Merge ๋ฒ„ํŠผ๋งŒ ๋ˆ„๋ฅด๋ฉด ์ž๋™์œผ๋กœ ์„œ๋ฒ„๊นŒ์ง€ ์—…๋ฐ์ดํŠธ๋˜๋‹ˆ
    • ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐฐํฌํ•˜๋Š” ๋ฌธํ™” ์ž์ฒด๊ฐ€ ํ›จ์”ฌ ์ž์—ฐ์Šค๋Ÿฌ์›Œ์ง„๋‹ค.

์ด์ œ ์˜จํ•์—์„œ ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค:

dev์— ์ฝ”๋“œ๋ฅผ ์˜ฌ๋ฆฌ๊ณ ,

PR์—์„œ CI ์ดˆ๋ก๋ถˆ ํ™•์ธํ•˜๊ณ ,

Merge โ†’ ์ž๋™ ๋ฐฐํฌ โ†’ onfit.today์—์„œ ๋ฐ”๋กœ ํ™•์ธ.

์ด ๋ฃจํ‹ด์ด ๋˜‘๊ฐ™์ด ๋ฐ˜๋ณต๋œ๋‹ค.

profile
์ฝ”๋ฆฐ์ด

0๊ฐœ์˜ ๋Œ“๊ธ€