๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
NestJS

[NestJS] PM2 ์†Œ๊ฐœ๋ถ€ํ„ฐ Docker + Github Action์„ ํ™œ์šฉํ•œ ๋ฐฐํฌ๊นŒ์ง€

by LasBe 2024. 11. 24.
๋ฐ˜์‘ํ˜•

๐Ÿ“’ [NestJS] PM2 ์†Œ๊ฐœ๋ถ€ํ„ฐ Docker + Github Action์„ ํ™œ์šฉํ•œ ๋ฐฐํฌ๊นŒ์ง€


์ผ๋ฐ˜์ ์œผ๋กœ NestJS ํ˜น์€ NextJS๋Š” Node.js์˜ ๋Ÿฐํƒ€์ž„ ์œ„์—์„œ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ๋งŽ์€ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ๋ณ‘๋ชฉํ˜„์ƒ์ด ๋ฐœ์ƒํ•ด ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ฐจ๋‹จ๋˜์–ด ์‘๋‹ต์„ฑ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿด ๋•Œ ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฉ€ํ‹ฐ ์ฝ”์–ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๊ฐ€ ์žˆ๋Š”๋ฐ์š”, ๋ฐ”๋กœ PM2์ž…๋‹ˆ๋‹ค.

 

๐Ÿ“Œ PM2๋ž€

PM2๋Š” Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœ๋•์…˜ ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €์ž…๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹คํ–‰, ๋ชจ๋‹ˆํ„ฐ๋ง, ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ, ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ๋“ฑ์„ ์ง€์›ํ•˜์—ฌ, Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•ˆ์ •์ ์œผ๋กœ ์šด์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.

ํŠนํžˆ, ์„œ๋ฒ„์—์„œ Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ๋•Œ ํ•„์š”ํ•œ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ํ†ตํ•ฉ์ ์œผ๋กœ ์ œ๊ณตํ•˜์—ฌ ๊ฐœ๋ฐœ์ž์™€ ์šด์˜์ž์˜ ํŽธ์˜์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.

 

๐Ÿ”Ž ์žฅ์ 

์ฃผ์š”ํ•œ ํŠน์žฅ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘, ์ค‘์ง€, ์žฌ์‹œ์ž‘, ์‚ญ์ œ ๋“ฑ์˜ ์ž‘์—…์„ ๋ช…๋ น์–ด๋กœ ๊ฐ„๋‹จํžˆ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ”„๋กœ์„ธ์Šค ํฌ๋ž˜์‹œ ์‹œ ์ž๋™ ์žฌ์‹œ์ž‘์„ ์ง€์›ํ•˜์—ฌ ์„œ๋น„์Šค ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์ง€์›
    • ๋ฉ€ํ‹ฐ ์ฝ”์–ด ์„œ๋ฒ„์—์„œ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•˜๋ฉด CPU๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค์˜ ํ•œ๊ณ„๋ฅผ ๋„˜์–ด์„œ ๋” ๋งŽ์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ (Zero Downtime Reload)
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ ๋„ ์ƒˆ ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • pm2 reload ๋ช…๋ น์œผ๋กœ ์„œ๋น„์Šค ์ค‘๋‹จ ์—†์ด ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ ๊ด€๋ฆฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹คํ–‰ ๋กœ๊ทธ(ํ‘œ์ค€ ์ถœ๋ ฅ ๋ฐ ์—๋Ÿฌ)๋ฅผ ์ž๋™์œผ๋กœ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    • ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜(pm2 logs), ๋กœ๊ทธ ํŒŒ์ผ์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” CLI ๋˜๋Š” ์›น ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ
    • JSON ๋˜๋Š” ecosystem.config.js ํŒŒ์ผ๋กœ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋‹ค์–‘ํ•œ ์‹คํ–‰ ํ™˜๊ฒฝ(dev, staging, production)์„ ์‰ฝ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
    • pm2 monit ๋ช…๋ น์œผ๋กœ CPU, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰, ์‹คํ–‰ ์ƒํƒœ ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • PM2 Plus์™€ ๊ฐ™์€ ์ถ”๊ฐ€ ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ๋” ์ƒ์„ธํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฐฐํฌ ์ž๋™ํ™”
    • pm2 deploy ๋ช…๋ น์„ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์›๊ฒฉ ์„œ๋ฒ„์— ์‰ฝ๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • Git ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์™€ ์—ฐ๋™ํ•˜์—ฌ ์ž๋™์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€ ๋ฐฐํฌํ•˜๋Š” ์›Œํฌํ”Œ๋กœ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

node ์ƒํƒœ๊ณ„์— ๋„ˆ๋ฌด๋‚˜ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ๊ฝ‰๊ฝ‰ ์ฑ„์›Œ ๋„ฃ์€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ”Ž ์ฃผ์š” ๋ช…๋ น์–ด

pm2๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ์‚ฌ์ „์— ์„ค์น˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

$ npm i -g pm2

 

  • ์•ฑ ์‹คํ–‰
$ pm2 start app.js --name my-app

 

  • ํ™˜๊ฒฝ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•œ ์•ฑ ์‹คํ–‰
$ pm2 start ecosystem.config.js

  • ์•ฑ ์ค‘์ง€
$ pm2 stop my-app
$ pm2 delete my-app

 

  • pm2 ์ข…๋ฃŒ
$ pm2 kill

  • ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง
$ pm2 monit

  • ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ํ™•์ธ
$ pm2 logs

 

๐Ÿ“Œ ํ™˜๊ฒฝ ํŒŒ์ผ (ecosystem.config.js)

PM2 ํ™˜๊ฒฝ ํŒŒ์ผ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ecosystem.config.js ๋˜๋Š” ecosystem.json ์ด๋ฆ„์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉฐ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์ธ ์†์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

module.exports = {
  apps: [
    {
      // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„ (PM2์—์„œ ์‚ฌ์šฉํ•  ์ด๋ฆ„)
      // PM2 ๋ช…๋ น์–ด์—์„œ ์ด ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
      name: "my-app",

      // ์‹คํ–‰ํ•  ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ๊ฒฝ๋กœ
      // Node.js ํŒŒ์ผ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, Bash, Python ๋“ฑ ๋‹ค๋ฅธ ์Šคํฌ๋ฆฝํŠธ๋„ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
      script: "./dist/main.js",

      // ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ (๊ธฐ๋ณธ๊ฐ’: ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ)
      // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ๊ธฐ์ค€์ด ๋˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
      cwd: "/home/user/my-app",

      // ์‹คํ–‰ ๋ชจ๋“œ: "fork" ๋˜๋Š” "cluster"
      // fork: ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ์‹คํ–‰
      // cluster: ๋ฉ€ํ‹ฐ ์ฝ”์–ด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค ์‹คํ–‰
      exec_mode: "cluster",

      // ์‹คํ–‰ํ•  ์ธ์Šคํ„ด์Šค ์ˆ˜
      // cluster ๋ชจ๋“œ์—์„œ๋งŒ ์œ ํšจํ•˜๋ฉฐ, "max"๋กœ ์„ค์ •ํ•˜๋ฉด ๋ชจ๋“  CPU ์ฝ”์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
      instances: "max",

      // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•  ์ธ์ˆ˜
      // ์‹คํ–‰ ์‹œ ์ปค๋งจ๋“œ๋ผ์ธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
      args: ["--port", "3000"],

      // ๊ธฐ๋ณธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
      // NODE_ENV, PORT ๋“ฑ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
      env: {
        NODE_ENV: "development", // ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
        PORT: 3000              // ๊ธฐ๋ณธ ํฌํŠธ
      },

      // ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
      // "pm2 start ecosystem.config.js --env production"์œผ๋กœ ์‹คํ–‰ ์‹œ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
      env_production: {
        NODE_ENV: "production", // ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ
        PORT: 8000              // ํ”„๋กœ๋•์…˜ ํฌํŠธ
      },

      // ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ • (ํ‘œ์ค€ ์ถœ๋ ฅ๊ณผ ์—๋Ÿฌ ๋กœ๊ทธ ํ†ตํ•ฉ)
      log_file: "/var/logs/my-app-combined.log",

      // ํ‘œ์ค€ ์ถœ๋ ฅ ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ
      out_file: "/var/logs/my-app-out.log",

      // ์—๋Ÿฌ ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ
      error_file: "/var/logs/my-app-error.log",

      // ํŒŒ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€ (๊ธฐ๋ณธ๊ฐ’: false)
      // true๋กœ ์„ค์ •ํ•˜๋ฉด ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž๋™์œผ๋กœ ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
      watch: true,

      // ๊ฐ์‹œ์—์„œ ์ œ์™ธํ•  ํŒŒ์ผ/ํด๋” (watch๊ฐ€ true์ผ ๋•Œ ์œ ํšจ)
      ignore_watch: ["node_modules", "logs"],

      // ์ž๋™ ์žฌ์‹œ์ž‘ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: true)
      // false๋กœ ์„ค์ •ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ข…๋ฃŒ๋œ ์ƒํƒœ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
      autorestart: true,

      // ์žฌ์‹œ์ž‘ ์‹œ๋„ ํšŸ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: ๋ฌด์ œํ•œ)
      // ํŠน์ • ํšŸ์ˆ˜ ์ด์ƒ ์‹คํŒจํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
      max_restarts: 10,

      // ์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ (๊ธฐ๋ณธ๊ฐ’: ์ œํ•œ ์—†์Œ)
      // ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ด ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์„ ์ดˆ๊ณผํ•˜๋ฉด ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
      max_memory_restart: "300M",

      // ๊ฐœ๋ฐœ ์ค‘ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ƒ์„ธ ๋กœ๊ทธ ํ™œ์„ฑํ™” (๊ธฐ๋ณธ๊ฐ’: false)
      // true๋กœ ์„ค์ •ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๋ฅผ ๋” ๋งŽ์ด ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
      debug: false,

      // ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค์˜ CPU/๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ํ™•์ธํ•  ์ฃผ๊ธฐ (๊ธฐ๋ณธ๊ฐ’: 10์ดˆ)
      // PM2 Dashboard์™€ ๊ฐ™์€ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
      min_uptime: "1m",   // ์ตœ์†Œ ์‹คํ–‰ ์‹œ๊ฐ„ (ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ด ์‹œ๊ฐ„ ์ด์ „์— ์ข…๋ฃŒ๋˜๋ฉด ๋น„์ •์ƒ์œผ๋กœ ๊ฐ„์ฃผ)
      max_uptime: "24h",  // ์ตœ๋Œ€ ์‹คํ–‰ ์‹œ๊ฐ„ (์ด ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ํ”„๋กœ์„ธ์Šค๋ฅผ ์žฌ์‹œ์ž‘)

      // ์‚ฌ์šฉ์ž ์ •์˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ›„ ์‹คํ–‰
      post_start_script: "./scripts/postStart.sh"
    }
  ]
};

 

๐Ÿ“Œ Github Action + Docker + PM2๋กœ  NestJS๋ฐฐํฌํ•˜๊ธฐ

์•„๋ฌด๋ž˜๋„ ์š”์ฆ˜์€ ๋ฐฐํฌํ•  ๋•Œ ๋„์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜๊ฐ€ ๋˜์–ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐฐํฌํ•  ๋•Œ pm2์™€ ๋„์ปค๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์„ ์˜ˆ์‹œ๋กœ ๋“ค์–ด ์†Œ๊ฐœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

 

๐Ÿ”Ž ecosystem.config.js

module.exports = {
  apps: [
    {
      name: 'franchise-backend',
      script: './dist/main.js',
      exec_mode: 'cluster',
      instances: '2', // CPU ์ฝ”์–ด ์ˆ˜
      max_memory_restart: '1000M', // ํ”„๋กœ์„ธ์Šค์˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ 1000MB์— ๋„๋‹ฌํ•˜๋ฉด reload ์‹คํ–‰
      merge_logs: true, // ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์‚ฌ์šฉ ์‹œ ๊ฐ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์ƒ์„ฑ๋˜๋Š” ๋กœ๊ทธ๋ฅผ ํ•œ ํŒŒ์ผ๋กœ ํ•ฉ์นจ
      autorestart: true, // ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ์‹œ ์ž๋™์œผ๋กœ ์žฌ์‹œ์ž‘ํ• ์ง€ ์„ ํƒ
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
        DATABASE_URL: 'VAL_DATABASE_URL',
        OPENAPI_KEY: 'VAL_OPENAPI_KEY',
        DEFAULT_YEAR: 'VAL_DEFAULT_YEAR',
      },
    },
  ],
};

๋จผ์ € ๋ฐฑ์•ค๋“œ ์•ฑ์„ ์–ด๋–ป๊ฒŒ ์‹คํ–‰ํ• ์ง€ pm2 ์„ค์ •ํŒŒ์ผ์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด env_production ๋ธ”๋Ÿญ ์•ˆ์— ์ถ”๊ฐ€์ ์œผ๋กœ ์ž‘์„ฑํ•ด ์ค๋‹ˆ๋‹ค.

์ €๋Š” ์ฒ˜๋ฆฌ๋Ÿ‰์ด ํฌ๊ฒŒ ๋งŽ์„ ๊ฒƒ ๊ฐ™์ง€ ์•Š์•„ ์šฐ์„  ์ฝ”์–ด๋ฅผ 2๊ฐœ ์ •๋„๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ”Ž Dockerfile

FROM node:22-alpine AS base

FROM base AS builder
  RUN apk add --no-cache libc6-compat
  WORKDIR /app
  COPY package.json pnpm-lock.yaml ./
  RUN corepack enable pnpm 
  RUN pnpm i --frozen-lockfile
  COPY . .
  ARG VAL_DATABASE_URL
  ARG VAL_OPENAPI_KEY
  ARG VAL_DEFAULT_YEAR
  RUN sed -i "s|VAL_DATABASE_URL|$VAL_DATABASE_URL|" ecosystem.config.js
  RUN sed -i "s|VAL_OPENAPI_KEY|$VAL_OPENAPI_KEY|" ecosystem.config.js
  RUN sed -i "s|VAL_DEFAULT_YEAR|$VAL_DEFAULT_YEAR|" ecosystem.config.js
  RUN pnpm build

FROM base AS runner
  WORKDIR /app
  RUN npm install -g pm2
  COPY --from=builder /app .
  RUN npm run prisma:generate
  EXPOSE 3000
  CMD ["pm2-runtime", "start", "ecosystem.config.js", "--env", "production"]

pnpm ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋ฅผ ์ด์šฉํ•œ ๋นŒ๋“œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

  • sed ๋ช…๋ น์–ด๋กœ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ์—์„œ ์ฃผ์ž…๋ฐ›์€ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ecosystem์— ์น˜ํ™˜ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ์‹คํ–‰ ๋ถ€๋ถ„์—์„œ pm2๋ฅผ ์„ค์น˜ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  • pm2 start๊ฐ€ ์•„๋‹Œ ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋˜์–ด
    ๋ฐ๋ชฌ ์—†์ด ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๋Š” pm2-runtime start๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ”Ž Github Action Workflow

name: deploy

on:
  push:
    branches: [main]

jobs:
  app-build:
    runs-on: ubuntu-latest
    env:
      DOCKER_IMAGE_TAG: ${{ secrets.DOCKER_REPOSITORY }}/${{secrets.DOCKER_IMAGENAME}}:latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

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

      - name: Login docker hub
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.DOCKER_REPOSITORY }}
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ${{ env.DOCKER_IMAGE_TAG }}
          build-args: |
            VAL_DATABASE_URL=${{secrets.VAL_DATABASE_URL}}
            VAL_OPENAPI_KEY=${{secrets.VAL_OPENAPI_KEY}}
            VAL_DEFAULT_YEAR=${{secrets.VAL_DEFAULT_YEAR}}

      - name: execute remote ssh
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USERNAME }}
          port: ${{ secrets.SERVER_PORT }}
          key: ${{ secrets.SERVER_KEY }}
          script: |
            docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
            docker pull ${{ env.DOCKER_IMAGE_TAG }}
            docker stop ${{secrets.DOCKER_IMAGENAME}} || true
            docker rm ${{secrets.DOCKER_IMAGENAME}} || true
            docker run -d --restart=always --name ${{secrets.DOCKER_IMAGENAME}} -p 3001:3000 ${{ env.DOCKER_IMAGE_TAG }}

์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œ ๋ฐ docker private repository์— push ํ•˜๊ณ , ssh ๋ช…๋ น์–ด๋กœ ๋ฐฐํฌ ์„œ๋ฒ„์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์€ ํ›„ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ณผ์ •์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋“ค์€ docker/build-push-action์—์„œ build-args๋ฅผ ํ†ตํ•ด ๋„์ปค ํŒŒ์ผ๋กœ ์ „๋‹ฌํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๐Ÿ”Ž ๊ฒฐ๊ณผ

์ปจํ…Œ์ด๋„ˆ์— exec ๋ช…๋ น์–ด๋กœ ์ ‘์† ํ›„ pm2 monit์„ ํ†ตํ•ด ๋ชจ๋‹ˆํ„ฐ๋งํ•ด๋ณด๋‹ˆ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์ •์ƒ์ ์œผ๋กœ ๊ฐ ํ”„๋กœ์„ธ์Šค์— ๋ถ„๋ฐฐํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€


์˜คํ”ˆ ์ฑ„ํŒ