GitHub Actions๋กœ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๋งŒ๋“ค๊ธฐ

GitHub Actions๋กœ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๋งŒ๋“ค๊ธฐ — ๋นŒ๋“œ๋ถ€ํ„ฐ ๋ฐฐํฌ๊นŒ์ง€. ์ฝ”๋“œ๋ฅผ pushํ•˜๋ฉด ์•Œ์•„์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ , ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ด ์˜ฌ๋ฆฌ๊ณ , ๋ฐฐํฌ๊นŒ์ง€ ํ•œ๋‹ค. ์†์œผ๋กœ ํ•˜๋˜ ๋ฐฐํฌ๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” GitHub Actions๋ฅผ, ์›Œํฌํ”Œ๋กœ ๊ตฌ์กฐ๋ถ€ํ„ฐ ์‹ค์ œ .yml๊ณผ ์‹คํ–‰ ๊ฒฐ๊ณผ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ •๋ฆฌํ•œ๋‹ค.


๋“ค์–ด๊ฐ€๋ฉฐ

Docker ์ž…๋ฌธ ๊ธ€์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค๊ณ  ์ปจํ…Œ์ด๋„ˆ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ฒ•์„ ๋‹ค๋ค˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์— ์ด๋ ‡๊ฒŒ ์ ์—ˆ๋‹ค — "๋‹ค์Œ ๋‹จ๊ณ„๋Š” ์ด ์ด๋ฏธ์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋นŒ๋“œ·๋ฐฐํฌํ•˜๋Š” CI/CD"๋ผ๊ณ . ๊ทธ ๋‹ค์Œ ๋‹จ๊ณ„๊ฐ€ ์ด ๊ธ€์ด๋‹ค.

์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค ์ค„ ์•Œ๊ฒŒ ๋์–ด๋„, ๋งค๋ฒˆ ์†์œผ๋กœ ํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ํ˜๋Ÿฌ๊ฐ„๋‹ค. ์ฝ”๋“œ๋ฅผ ๊ณ ์น˜๊ณ , ๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ (๊ฐ€๋” ๊นœ๋นกํ•˜๊ณ ), docker build ์น˜๊ณ , ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— pushํ•˜๊ณ , ์„œ๋ฒ„์— SSH๋กœ ๋“ค์–ด๊ฐ€์„œ pullํ•˜๊ณ  ์žฌ์‹œ์ž‘ํ•œ๋‹ค. ์‚ฌ๋žŒ์ด ํ•˜๋Š” ์ผ์ด๋ผ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ , ํƒœ๊ทธ๋ฅผ ํ—ท๊ฐˆ๋ฆฌ๊ณ , ๋ฐฐํฌ ์ˆœ์„œ๋ฅผ ๋น ๋œจ๋ฆฐ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค ๊ธˆ์š”์ผ ์ €๋…์— ์‚ฌ๊ณ ๊ฐ€ ๋‚œ๋‹ค.

CI/CD์˜ ํ•ต์‹ฌ์€ "์‚ฌ๋žŒ์ด ๋งค๋ฒˆ ๋ฐ˜๋ณตํ•˜๋˜ ์ผ์„ ์ฝ”๋“œ๋กœ ๋ชป ๋ฐ•์•„ ์ž๋™์œผ๋กœ ๋Œ๋ฆฐ๋‹ค"๋Š” ๊ฒƒ์ด๋‹ค. ๋นŒ๋“œ·ํ…Œ์ŠคํŠธ·๋ฐฐํฌ๊ฐ€ ์ฝ”๋“œ๊ฐ€ ๋˜๋ฉด, ๋น ๋œจ๋ฆด ์ˆ˜๋„ ํ—ท๊ฐˆ๋ฆด ์ˆ˜๋„ ์—†๋‹ค.

GitHub Actions๋Š” ์ด ์ž๋™ํ™”๋ฅผ GitHub ์ €์žฅ์†Œ ์•ˆ์—์„œ ๋ฐ”๋กœ ํ•ด์ฃผ๋Š” ๋„๊ตฌ๋‹ค. ๋ณ„๋„ ์„œ๋ฒ„๋ฅผ ์„ธ์šธ ํ•„์š” ์—†์ด, ์ €์žฅ์†Œ์— .yml ํŒŒ์ผ ํ•˜๋‚˜๋ฅผ ๋‘๋ฉด push·PR ๊ฐ™์€ ์ด๋ฒคํŠธ์— ๋งž์ถฐ ์›Œํฌํ”Œ๋กœ๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” CI/CD๊ฐ€ ๋ฌด์—‡์ธ์ง€๋ถ€ํ„ฐ, ์›Œํฌํ”Œ๋กœ์˜ ๊ตฌ์กฐ, ๋ฆฐํŠธ·ํ…Œ์ŠคํŠธ ์ž๋™ํ™”(CI), Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ·ํ‘ธ์‹œ, ๊ทธ๋ฆฌ๊ณ  ๋ฐฐํฌ(CD)๊นŒ์ง€ ์‹ค์ œ .yml๊ณผ ํ•จ๊ป˜ ๋‹จ๊ณ„๋ณ„๋กœ ๋งŒ๋“ ๋‹ค.


1. CI/CD๋ž€ — ๊ทธ๋ฆฌ๊ณ  GitHub Actions

๋จผ์ € ์šฉ์–ด๋ถ€ํ„ฐ ์ •๋ฆฌํ•˜์ž.

  • CI (Continuous Integration, ์ง€์†์  ํ†ตํ•ฉ) — ์ฝ”๋“œ๋ฅผ ์ž์ฃผ ํ•ฉ์น˜๊ณ , ํ•ฉ์น  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋นŒ๋“œ·ํ…Œ์ŠคํŠธํ•ด์„œ ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ์žก๋Š” ๊ฒƒ. "์ด ๋ณ€๊ฒฝ์ด ๋ญ”๊ฐ€ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š์•˜๋‚˜?"๋ฅผ ๋งค push๋งˆ๋‹ค ๊ธฐ๊ณ„๊ฐ€ ๊ฒ€์‚ฌํ•œ๋‹ค.
  • CD (Continuous Delivery/Deployment, ์ง€์†์  ์ „๋‹ฌ/๋ฐฐํฌ) — CI๋ฅผ ํ†ต๊ณผํ•œ ๊ฒฐ๊ณผ๋ฌผ์„ ์ž๋™์œผ๋กœ ๋ฐฐํฌ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, ์‹ค์ œ๋กœ ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ. ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•œ ๋ฐ”๋กœ ๊ทธ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค.

ํ•œ ๋ฌธ์žฅ์œผ๋กœ, CI๋Š” "๊นจ์ง„ ๊ฑธ ์ผ์ฐ ์žก๋Š” ๊ทธ๋ฌผ", CD๋Š” "๊ฒ€์ฆ๋œ ๊ฑธ ์ž๋™์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š” ์ปจ๋ฒ ์ด์–ด"๋‹ค.

GitHub Actions๋Š” ์ด ๋‘˜์„ GitHub์— ๋‚ด์žฅ๋œ ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•œ๋‹ค. ๋™์ž‘์„ ์ดํ•ดํ•˜๋ ค๋ฉด ๋‹ค์„ฏ ๋‹จ์–ด๋ฉด ๋œ๋‹ค.

  • Workflow — ์ž๋™ํ™” ์ „์ฒด. .github/workflows/ ์•ˆ์˜ .yml ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ ์›Œํฌํ”Œ๋กœ ํ•˜๋‚˜๋‹ค.
  • Event — ์›Œํฌํ”Œ๋กœ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ๋ฐฉ์•„์‡ . push, pull_request, ํƒœ๊ทธ ์ƒ์„ฑ ๋“ฑ.
  • Job — ์›Œํฌํ”Œ๋กœ ์•ˆ์˜ ์ž‘์—… ๋‹จ์œ„. ์—ฌ๋Ÿฌ Job์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ณ‘๋ ฌ ์‹คํ–‰๋˜๋ฉฐ, ์˜์กด ๊ด€๊ณ„๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • Step — Job ์•ˆ์˜ ํ•œ ๋‹จ๊ณ„. ์•ก์…˜์„ ํ˜ธ์ถœ(uses)ํ•˜๊ฑฐ๋‚˜ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰(run)ํ•œ๋‹ค.
  • Runner — Job์„ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฐ€์ƒ ๋จธ์‹ (๋ณดํ†ต ubuntu-latest). ๋งค๋ฒˆ ๊นจ๋—ํ•œ ํ™˜๊ฒฝ์—์„œ ์‹œ์ž‘ํ•œ๋‹ค.

2. ์›Œํฌํ”Œ๋กœ์˜ ๊ตฌ์กฐ — YAML ํ•œ ์žฅ ๋œฏ์–ด๋ณด๊ธฐ

์›Œํฌํ”Œ๋กœ ํŒŒ์ผ์€ ์ €์žฅ์†Œ์˜ .github/workflows/ ํด๋”์— ๋‘”๋‹ค. ๊ฐ€์žฅ ์ž‘์€ ์˜ˆ์ œ๋กœ ๊ตฌ์กฐ๋ฅผ ๋ณด์ž.

# .github/workflows/ci.yml
name: CI                       # ์›Œํฌํ”Œ๋กœ ์ด๋ฆ„ (Actions ํƒญ์— ํ‘œ์‹œ)

on:                            # ์–ธ์ œ ์‹คํ–‰ํ• ์ง€ — ์ด๋ฒคํŠธ
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:                        # job ์ด๋ฆ„
    runs-on: ubuntu-latest     # ์–ด๋–ค runner์—์„œ ๋Œ๋ฆด์ง€
    steps:
      - uses: actions/checkout@v5      # 1) ์ €์žฅ์†Œ ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ
      - uses: actions/setup-node@v5    # 2) Node.js ์„ค์น˜
        with:
          node-version: "20"
          cache: "npm"                 # ์˜์กด์„ฑ ์บ์‹œ (๋นŒ๋“œ ๋‹จ์ถ•)
      - run: npm ci                    # 3) ์˜์กด์„ฑ ์„ค์น˜
      - run: npm test                  # 4) ํ…Œ์ŠคํŠธ ์‹คํ–‰

์ฝ๋Š” ๋ฒ•์€ ์œ„์—์„œ ์•„๋ž˜๋กœ ๊ทธ๋Œ€๋กœ๋‹ค. on์ด ์–ธ์ œ, jobs๊ฐ€ ๋ฌด์—‡์„, steps๊ฐ€ ์–ด๋–ค ์ˆœ์„œ๋กœ๋ฅผ ์ •์˜ํ•œ๋‹ค. uses๋Š” ๋‚จ์ด ๋งŒ๋“  ์žฌ์‚ฌ์šฉ ์•ก์…˜์„ ๊ฐ€์ ธ๋‹ค ์“ฐ๋Š” ๊ฒƒ์ด๊ณ (actions/checkout์ฒ˜๋Ÿผ), run์€ ์…ธ ๋ช…๋ น์„ ์ง์ ‘ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด ํŒŒ์ผ์„ ์ €์žฅ์†Œ์— pushํ•˜๋ฉด, ๊ทธ ์ˆœ๊ฐ„๋ถ€ํ„ฐ main์— ๋Œ€ํ•œ push์™€ PR๋งˆ๋‹ค GitHub๊ฐ€ ์ž๋™์œผ๋กœ ์ด ์›Œํฌํ”Œ๋กœ๋ฅผ ๋Œ๋ฆฐ๋‹ค. ์ €์žฅ์†Œ์˜ Actions ํƒญ์—์„œ ์‹คํ–‰ ์ด๋ ฅ๊ณผ ๋กœ๊ทธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋ฒ„์ „ ๋ฉ”๋ชจ. ์•ก์…˜ ๋ฒ„์ „(@v5, @v4 ๋“ฑ)์€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์˜ฌ๋ผ๊ฐ„๋‹ค. ํ•ญ์ƒ ๋ฉ”์ด์ € ํƒœ๊ทธ๋กœ ํ•€ํ•˜๊ณ (@main์ฒ˜๋Ÿผ ๋– ๋‹ค๋‹ˆ๋Š” ์ฐธ์กฐ๋Š” ํ”ผํ•œ๋‹ค), ์‹ค์ œ ์ ์šฉ ์‹œ GitHub Marketplace์—์„œ ์ตœ์‹  ๋ฉ”์ด์ € ๋ฒ„์ „์„ ํ•œ ๋ฒˆ ํ™•์ธํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค.


3. CI ํŒŒ์ดํ”„๋ผ์ธ — ๋ฆฐํŠธ·ํ…Œ์ŠคํŠธ ์ž๋™ํ™”

์ด์ œ ์‹ค์šฉ์ ์ธ CI๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. PR์ด ์˜ฌ๋ผ์˜ฌ ๋•Œ๋งˆ๋‹ค ๋ฆฐํŠธ → ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค์„œ, ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋จธ์ง€๋ฅผ ๋ง‰๋Š” ๊ฒŒ ๋ชฉํ‘œ๋‹ค.

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# ๊ฐ™์€ ๋ธŒ๋žœ์น˜์— ์ƒˆ push๊ฐ€ ์˜ค๋ฉด ์ง„ํ–‰ ์ค‘์ด๋˜ ๋‚ก์€ ์‹คํ–‰์€ ์ทจ์†Œ
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: ["20", "22"]      # ๋‘ Node ๋ฒ„์ „์—์„œ ๋™์‹œ์— ๊ฒ€์ฆ
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: ${{ matrix.node }}
          cache: "npm"
      - run: npm ci
      - run: npm run lint        # ESLint ๋“ฑ
      - run: npm test            # ํ…Œ์ŠคํŠธ

๋ช‡ ๊ฐ€์ง€ ์‹ค๋ฌด ์žฅ์น˜๊ฐ€ ๋“ค์–ด ์žˆ๋‹ค.

  • matrixnode: ["20", "22"]๋กœ ์—ฌ๋Ÿฌ ๋ฒ„์ „์—์„œ ๋ณ‘๋ ฌ๋กœ ๊ฐ™์€ ๊ฒ€์ฆ์„ ๋Œ๋ฆฐ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๋‹ค์ค‘ ํ™˜๊ฒฝ ์ง€์›์— ์œ ์šฉํ•˜๋‹ค.
  • concurrency — ๋น ๋ฅด๊ฒŒ ์—ฐ์† pushํ•  ๋•Œ, ์ด๋ฏธ ๋Œ๊ณ  ์žˆ๋˜ ๋‚ก์€ ์‹คํ–‰์„ ์ž๋™ ์ทจ์†Œํ•ด ๋‚ญ๋น„๋ฅผ ์ค„์ธ๋‹ค.
  • cache: "npm" — ์˜์กด์„ฑ์„ ์บ์‹œํ•ด npm ci ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ์ค„์ธ๋‹ค.

DB๊ฐ€ ํ•„์š”ํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ผ๋ฉด, services๋กœ ํ…Œ์ŠคํŠธ์šฉ ์ปจํ…Œ์ด๋„ˆ(์˜ˆ: PostgreSQL)๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ Docker์—์„œ ๋ฐฐ์šด ์ปจํ…Œ์ด๋„ˆ ๊ฐœ๋…์ด ๊ทธ๋Œ€๋กœ ์“ฐ์ธ๋‹ค.

    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

pushํ•˜๋ฉด Actions ํƒญ์— ๊ฒฐ๊ณผ๊ฐ€ ๋œฌ๋‹ค. ํ†ต๊ณผํ•˜๋ฉด ์ด๋Ÿฐ ๋ชจ์Šต์ด๋‹ค.

โœ“ test (20)     1m 38s
โœ“ test (22)     1m 41s
All checks have passed

ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ๋นจ๊ฐ„ X๊ฐ€ ๋œจ๊ณ , ๋ธŒ๋žœ์น˜ ๋ณดํ˜ธ ๊ทœ์น™(๋’ค์—์„œ ์„ค๋ช…)๊ณผ ๋ฌถ์œผ๋ฉด ๊ทธ PR์€ ๋จธ์ง€๊ฐ€ ๋ง‰ํžŒ๋‹ค. "ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ์—†์ด๋Š” main์— ๋ชป ๋“ค์–ด์˜จ๋‹ค"๊ฐ€ ์ฝ”๋“œ๋กœ ๊ฐ•์ œ๋œ๋‹ค.


4. ์ด๋ฏธ์ง€ ๋นŒ๋“œ & ํ‘ธ์‹œ — Docker์™€ ์—ฐ๊ฒฐ

CI๊ฐ€ ์ดˆ๋ก๋ถˆ์ด๋ฉด, ์ด์ œ Docker ์ž…๋ฌธ์—์„œ ๋งŒ๋“  Dockerfile๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ด ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ์˜ฌ๋ฆฐ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” GitHub๊ฐ€ ๋ฌด๋ฃŒ๋กœ ์ œ๊ณตํ•˜๋Š” GitHub Container Registry(ghcr.io)์— ํ‘ธ์‹œํ•œ๋‹ค.

# .github/workflows/docker.yml
name: Build and Push Image

on:
  push:
    branches: [main]
    tags: ["v*.*.*"]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}   # ์˜ˆ: jinoism/myapp

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write              # ghcr.io ํ‘ธ์‹œ์— ํ•„์š”
    steps:
      - uses: actions/checkout@v5

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}   # ์ž๋™ ์ œ๊ณต — ๋ณ„๋„ ์„ค์ • ๋ถˆํ•„์š”

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch          # ๋ธŒ๋žœ์น˜๋ช… ํƒœ๊ทธ
            type=semver,pattern={{version}} # v1.2.3 ํƒœ๊ทธ
            type=sha,prefix=sha-            # ์ปค๋ฐ‹ SHA ํƒœ๊ทธ

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha           # GitHub Actions ์บ์‹œ ํ™œ์šฉ
          cache-to: type=gha,mode=max

ํ•ต์‹ฌ ํฌ์ธํŠธ๋งŒ ์งš์ž.

  • permissions: packages: writeghcr.io์— ํ‘ธ์‹œํ•˜๋ ค๋ฉด ์ด ๊ถŒํ•œ์ด ํ•„์š”ํ•˜๋‹ค.
  • secrets.GITHUB_TOKEN — GitHub๊ฐ€ ์›Œํฌํ”Œ๋กœ๋งˆ๋‹ค ์ž๋™ ๋ฐœ๊ธ‰ํ•˜๋Š” ํ† ํฐ์ด๋ผ, GHCR ๋กœ๊ทธ์ธ์— ๋ณ„๋„ ๋น„๋ฐ€ํ‚ค๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • metadata-action — Git ์ •๋ณด(๋ธŒ๋žœ์น˜·ํƒœ๊ทธ·SHA)๋กœ ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•œ๋‹ค. ์†์œผ๋กœ ํƒœ๊ทธ๋ฅผ ๋ถ™์ด๋‹ค ์‹ค์ˆ˜ํ•˜๋Š” ์ผ์ด ์‚ฌ๋ผ์ง„๋‹ค.
  • cache-from/to: gha — ์ด์ „ ๋นŒ๋“œ์˜ ๋ ˆ์ด์–ด ์บ์‹œ๋ฅผ ์žฌ์‚ฌ์šฉํ•ด ๋นŒ๋“œ๋ฅผ ๋‹จ์ถ•ํ•œ๋‹ค(๋ ˆ์ด์–ด ์บ์‹œ ๊ฐœ๋…์€ Docker ๊ธ€ ์ฐธ๊ณ ).

์„ฑ๊ณตํ•˜๋ฉด ์ €์žฅ์†Œ์˜ Packages์— ์ด๋ฏธ์ง€๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค.

#9 exporting to image
#9 pushing ghcr.io/jinoism/myapp:main
#9 pushing ghcr.io/jinoism/myapp:sha-a1b2c3d
#9 DONE

์ด์ œ docker pull ghcr.io/jinoism/myapp:main์œผ๋กœ ์–ด๋””์„œ๋“  ๊ฐ™์€ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.


5. CD — ๋ฐฐํฌ ์ž๋™ํ™”์™€ ์‹œํฌ๋ฆฟ

๋งˆ์ง€๋ง‰ ์กฐ๊ฐ์ด๋‹ค. ๋นŒ๋“œ๋œ ์ด๋ฏธ์ง€๋ฅผ ์‹ค์ œ ์„œ๋ฒ„์— ๋ฐฐํฌํ•œ๋‹ค. ํ•ต์‹ฌ์€ "ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•œ ๋ฐ”๋กœ ๊ทธ ์ด๋ฏธ์ง€๋ฅผ, ๊ฒ€์ฆ์„ ๊ฑฐ์ณ ๋‚ด๋ณด๋‚ธ๋‹ค"๋Š” ํ๋ฆ„์ด๋‹ค.

Job ์‚ฌ์ด ์ˆœ์„œ๋Š” needs๋กœ ๋ชป ๋ฐ•๋Š”๋‹ค. test๊ฐ€ ํ†ต๊ณผํ•ด์•ผ build๊ฐ€ ๋Œ๊ณ , build๊ฐ€ ๋๋‚˜์•ผ deploy๊ฐ€ ๋ˆ๋‹ค.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # ... (3๋ฒˆ์˜ CI ๋‚ด์šฉ)

  build:
    needs: test            # test ํ†ต๊ณผ ํ›„์—๋งŒ
    runs-on: ubuntu-latest
    steps:
      # ... (4๋ฒˆ์˜ ๋นŒ๋“œ·ํ‘ธ์‹œ ๋‚ด์šฉ)

  deploy:
    needs: build           # build ์„ฑ๊ณต ํ›„์—๋งŒ
    runs-on: ubuntu-latest
    environment: production   # ์Šน์ธ ๊ฒŒ์ดํŠธ (์•„๋ž˜ ์„ค๋ช…)
    steps:
      - name: Deploy over SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /srv/myapp
            docker compose pull        # ์ƒˆ ์ด๋ฏธ์ง€ ๋ฐ›๊ธฐ
            docker compose up -d        # ๋ฌด์ค‘๋‹จ์— ๊ฐ€๊น๊ฒŒ ๊ต์ฒด

๋ฐฐํฌ ๋‹จ๊ณ„์˜ ํ•ต์‹ฌ์€ ๋‘ ๊ฐ€์ง€๋‹ค.

์‹œํฌ๋ฆฟ(Secrets). ์„œ๋ฒ„ ์ฃผ์†Œ·์ ‘์† ํ‚ค ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ๊ฐ’์€ ์ ˆ๋Œ€ .yml์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ €์žฅ์†Œ Settings → Secrets and variables → Actions์— ๋“ฑ๋กํ•˜๊ณ , ์›Œํฌํ”Œ๋กœ์—์„œ๋Š” ${{ secrets.์ด๋ฆ„ }}์œผ๋กœ ์ฐธ์กฐํ•œ๋‹ค. ์ฝ”๋“œ์—๋Š” ๊ฐ’์ด ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๊ณ , ๋กœ๊ทธ์—๋„ ๋งˆ์Šคํ‚น๋œ๋‹ค.

์Šน์ธ ๊ฒŒ์ดํŠธ(Environment). environment: production์„ ์ง€์ •ํ•˜๊ณ  ๊ทธ ํ™˜๊ฒฝ์— ํ•„์ˆ˜ ๋ฆฌ๋ทฐ์–ด๋ฅผ ๊ฑธ์–ด๋‘๋ฉด, ์šด์˜ ๋ฐฐํฌ ์ง์ „์— "Approve"๋ฅผ ๋ˆŒ๋Ÿฌ์•ผ ์ง„ํ–‰๋œ๋‹ค. ์ž๋™ํ™”ํ•˜๋˜ ์šด์˜ ๋ฐฐํฌ๋งŒ์€ ์‚ฌ๋žŒ์ด ํ•œ ๋ฒˆ ํ™•์ธํ•˜๋Š”, ํ˜„์‹ค์ ์ธ ์ ˆ์ถฉ์ด๋‹ค.

์œ„ ์˜ˆ์‹œ๋Š” SSH๋กœ ์„œ๋ฒ„์— ๋“ค์–ด๊ฐ€ docker compose๋กœ ๊ต์ฒดํ•˜๋Š” ๋‹จ์ˆœํ•œ ๋ฐฉ์‹์ด๋‹ค. Vercel·AWS·Cloudflare ๊ฐ™์€ ํ”Œ๋žซํผ์€ ๊ฐ์ž์˜ ๊ณต์‹ ์•ก์…˜์ด๋‚˜ OIDC ์—ฐ๋™์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ, ๋ฐฐํฌ ๋Œ€์ƒ์— ๋งž๋Š” ์•ก์…˜์œผ๋กœ ์ด deploy Job๋งŒ ๊ฐˆ์•„๋ผ์šฐ๋ฉด ๋œ๋‹ค.


6. ์‹ค์ „ ํŒ — ๋” ๋น ๋ฅด๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ

๋งˆ์ง€๋ง‰์œผ๋กœ, ๊ตด๋ ค๋ณด๋ฉด ๊ณง ํ•„์š”ํ•ด์ง€๋Š” ๊ฒƒ๋“ค์„ ์ •๋ฆฌํ•œ๋‹ค.

  • ๋ธŒ๋žœ์น˜ ๋ณดํ˜ธ ๊ทœ์น™ — Settings → Branches์—์„œ main์— "์ƒํƒœ ์ฒดํฌ ํ†ต๊ณผ ํ•„์ˆ˜"๋ฅผ ๊ฑธ๋ฉด, CI๊ฐ€ ์ดˆ๋ก๋ถˆ์ด ์•„๋‹Œ PR์€ ๋จธ์ง€๊ฐ€ ๋ง‰ํžŒ๋‹ค. CI๋ฅผ "์žˆ์œผ๋ฉด ์ข‹์€ ๊ฒƒ"์—์„œ "ํ†ต๊ณผํ•ด์•ผ ํ•˜๋Š” ๊ณ„์•ฝ"์œผ๋กœ ๋ฐ”๊พผ๋‹ค.
  • ์บ์‹œcache: "npm"(์˜์กด์„ฑ)๊ณผ cache-from/to: gha(Docker ๋ ˆ์ด์–ด)๋งŒ ์ผœ๋„ ํŒŒ์ดํ”„๋ผ์ธ ์‹œ๊ฐ„์ด ์ ˆ๋ฐ˜ ์ดํ•˜๋กœ ์ค„๊ณค ํ•œ๋‹ค.
  • concurrency — ์—ฐ์† push ์‹œ ๋‚ก์€ ์‹คํ–‰์„ ์ž๋™ ์ทจ์†Œํ•ด runner ์‹œ๊ฐ„์„ ์•„๋‚€๋‹ค.
  • ๋””๋ฒ„๊น… — ์‹คํŒจํ•œ Job์˜ ๋กœ๊ทธ๋ฅผ ํŽผ์ณ ๋ณด๊ณ , ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ "Re-run failed jobs"๋กœ ์‹คํŒจ๋ถ„๋งŒ ๋‹ค์‹œ ๋Œ๋ฆฐ๋‹ค. ์ €์žฅ์†Œ ์‹œํฌ๋ฆฟ์— ACTIONS_STEP_DEBUG=true๋ฅผ ๋‘๋ฉด ๋” ์ž์„ธํ•œ ๋กœ๊ทธ๊ฐ€ ๋‚˜์˜จ๋‹ค.
  • ๊ถŒํ•œ ์ตœ์†Œํ™”permissions๋Š” ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋ช…์‹œํ•œ๋‹ค(์˜ˆ: ๋นŒ๋“œ Job์—๋งŒ packages: write). ๊ธฐ๋ณธ์„ ์ขํžˆ๊ณ  ํ•„์š”ํ•œ ๊ณณ๋งŒ ์—ฐ๋‹ค.

์•„๋ž˜๋Š” ์ž์ฃผ ์“ฐ๋Š” ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ •๋ฆฌํ•œ ํ‘œ๋‹ค.

ํŠธ๋ฆฌ๊ฑฐ ์–ธ์ œ ์‹คํ–‰๋˜๋‚˜
push (branches) ํŠน์ • ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œํ•  ๋•Œ
pull_request PR์ด ์—ด๋ฆฌ๊ฑฐ๋‚˜ ๊ฐฑ์‹ ๋  ๋•Œ
push (tags) v1.2.3 ๊ฐ™์€ ํƒœ๊ทธ๋ฅผ ํ‘ธ์‹œํ•  ๋•Œ (๋ฆด๋ฆฌ์Šค ๋ฐฐํฌ์— ์ ํ•ฉ)
workflow_dispatch Actions ํƒญ์—์„œ ์ˆ˜๋™ ์‹คํ–‰ ๋ฒ„ํŠผ์œผ๋กœ
schedule (cron) ์ •ํ•ด์ง„ ์‹œ๊ฐ๋งˆ๋‹ค (์•ผ๊ฐ„ ๋ฐฐ์น˜ ๋“ฑ)

๋งˆ์น˜๋ฉฐ

CI/CD๋Š” "์‚ฌ๋žŒ์ด ๋งค๋ฒˆ ๋ฐ˜๋ณตํ•˜๋˜ ๋นŒ๋“œ·ํ…Œ์ŠคํŠธ·๋ฐฐํฌ๋ฅผ ์ฝ”๋“œ๋กœ ๋ชป ๋ฐ•์•„ ์ž๋™ํ™”ํ•œ๋‹ค"๋Š” ๋ฐœ์ƒ์ด๋‹ค. GitHub Actions๋Š” ์ด๊ฑธ ์ €์žฅ์†Œ ์•ˆ์—์„œ .yml ํ•œ๋‘ ์žฅ์œผ๋กœ ํ•ด๊ฒฐํ•œ๋‹ค. pushํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๋Œ๊ณ (CI), ํ†ต๊ณผํ•˜๋ฉด ๊ฐ™์€ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ด ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ์˜ฌ๋ฆฌ๊ณ , ์Šน์ธ ํ›„ ๋ฐฐํฌ๋˜๋Š”(CD) ํ๋ฆ„์ด ํ•œ ๋ฒˆ ๊ฐ–์ถฐ์ง€๋ฉด, ๋ฐฐํฌ๋Š” ๋” ์ด์ƒ ๊ธด์žฅ๋˜๋Š” ์ˆ˜์ž‘์—…์ด ์•„๋‹ˆ๋ผ ์ผ์ƒ์ด ๋œ๋‹ค.

ํ•ต์‹ฌ๋งŒ ๋‹ค์‹œ ์งš์œผ๋ฉด, Event → Workflow → Job → Step ๊ตฌ์กฐ ์œ„์—์„œ, needs๋กœ ์ˆœ์„œ๋ฅผ ์ž‡๊ณ , ์‹œํฌ๋ฆฟ์œผ๋กœ ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ์ˆจ๊ธฐ๊ณ , ์บ์‹œ๋กœ ์†๋„๋ฅผ ์˜ฌ๋ฆฌ๊ณ , ์Šน์ธ ๊ฒŒ์ดํŠธ๋กœ ์•ˆ์ „์žฅ์น˜๋ฅผ ๋‘”๋‹ค. Docker์—์„œ ๋งŒ๋“  ์ด๋ฏธ์ง€๊ฐ€ ์—ฌ๊ธฐ์„œ ์ž๋™์œผ๋กœ ๋นŒ๋“œ·๋ฐฐํฌ๋˜๋ฉฐ ํ•˜๋‚˜์˜ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์ด์–ด์ง„๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„๋Š” ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๊ฑธ์ณ ์šด์˜ํ•˜๊ณ  ์ž๋™ ๋ณต๊ตฌ·ํ™•์žฅํ•˜๋Š” ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜, ์ฆ‰ Kubernetes๋‹ค. ๊ทธ๊ฑด ๋ณ„๋„ ๊ธ€์—์„œ ์ด์–ด๊ฐ€๊ฒ ๋‹ค.


์ฐธ๊ณ  ์ถœ์ฒ˜