๐Ÿ’ป ์‹ค๋ฌด DevOps ๊ธฐ์ดˆ ๋ฃจํŠธ ๋”ฐ๋ผํ•˜๊ธฐ โ€” Docker, DockerHub, GitHub Actions, AWS EC2๊นŒ์ง€

Atmosphereยท2025๋…„ 3์›” 31์ผ

DevOps

๋ชฉ๋ก ๋ณด๊ธฐ
1/6

chat gpt์˜ ๋„์›€์„ ๋ฐ›์œผ๋ฉฐ ์ผ๋‹จ ๋ฐฐํฌ ๋จผ์ € ํ•ด๋ณด์ž!๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ ํ•˜๋‚˜์”ฉ ํ•ด๋ณด๊ณ ์žˆ๋‹ค.

๋ชฉํ‘œ: Node.js + MongoDB ํ”„๋กœ์ ํŠธ๋ฅผ GitHub Actions + DockerHub + Docker Compose + AWS EC2๋ฅผ ํ™œ์šฉํ•ด ์™„์ „ ์ž๋™ํ™” + ์‹ค์ „ ๋ฐฐํฌ๊นŒ์ง€ ํ•ด๋ณด๊ธฐ

github ๋ ˆํฌ์ง€ํ† ๋ฆฌ 2๊ฐœ์— push ๋˜์–ด์žˆ๋‹ค.
devops-node-todo-api / compose-todo-api

devops-node-todo-api
compose-todo-api
์ฝ”๋“œ๋ฅผ ๊นƒํ—ˆ๋ธŒ์— ์˜ฌ๋ ค๋’€์œผ๋‹ˆ ์ฐธ๊ณ ํ•ด์„œ ๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

์ „์ฒด ์‹ค์Šต ํ๋ฆ„ ์š”์•ฝ

1. Node.js + MongoDB Todo API ๋งŒ๋“ค๊ธฐ

  • express์™€ mongoose๋กœ ๊ฐ„๋‹จํ•œ /todos API ๊ตฌํ˜„
  • GET, POST, PUT, DELETE ๋ผ์šฐํŠธ๋กœ CRUD ๊ตฌ์„ฑ
  • MongoDB๋Š” ๋กœ์ปฌ์ด ์•„๋‹Œ ๋„์ปค ๋‚ด๋ถ€์˜ mongo ์„œ๋น„์Šค๋กœ ์—ฐ๊ฒฐ
mongoose.connect('mongodb://mongo:27017/todos')

2. Docker๋กœ ๋ฐฑ์—”๋“œ ์ปจํ…Œ์ด๋„ˆํ™”

  • backend/Dockerfile ์ž‘์„ฑ
  • Node.js ์•ฑ์„ ๋„์ปค ์ด๋ฏธ์ง€๋กœ ๋นŒ๋“œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ตฌ์„ฑ
    Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]

์ดํ›„

 docker build -t devops-node-todo-api

๋ฅผ ์ด์šฉํ•ด ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ด์ฃผ๊ณ 

docker run -p 3000:3000 devops-node-todo-api

๋ฅผ ์จ์„œ ๋„์ปค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•ด์ค€๋‹ค.
-p 3000:3000 -> ๋‚ด ์ปดํ“จํ„ฐ์˜ 3000๋ฒˆ ํฌํŠธ๋ฅผ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์˜ 3000๋ฒˆ ํฌํŠธ์™€ ์—ฐ๊ฒฐ

3. Docker Compose๋กœ ์ „์ฒด ์„œ๋น„์Šค ๊ด€๋ฆฌ

  • docker-compose.yml๋กœ ๋ฐฑ์—”๋“œ + MongoDB๊ตฌ์„ฑ
    docker-compose.yml
services:
  backend:
    build: ./backend
    ports:
      - "3000:3000"
    depends_on:
      - mongo
  mongo:
    image: mongo
    ports:
      - "27017:27017"
  • docker-compose up ์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ 2๊ฐœ๋ฅผ ํ•œ๋ฒˆ์— ์‹คํ–‰
    ์ด๋•Œ ์ค‘๊ฐ„์— gpt๊ฐ€ ์•Œ๋ ค์ค€ index.js ์ฝ”๋“œ ์ค‘ ์ผ๋ถ€๋ฅผ ์ผ๋ถ€๋Ÿฌ ์•ˆ์ผ์—ˆ๋‹ค. ์•„์˜ˆ ์•ˆ์“ฐ๋Š”๊ฒŒ ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๊ธฐ๋„ ํ–ˆ๋˜..
mongoose.connect('mongodb://mongo:27017/todos', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

useNewUrlParser๋ž‘ useUnifiedTopology๋Š” MongoDB ์ตœ์‹  ๋ฒ„์ „์—์„œ๋Š” ํ•„์š”์—†๋Š” ์˜ต์…˜.
๊ทธ๋Ÿฌ๋‚˜ mongoose.connect() ์ž์ฒด๋Š” ํ•„์ˆ˜๋‹ค. Node.js ์„œ๋ฒ„๊ฐ€ MongoDB์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ/์ฝ๊ธฐ ํ•˜๋ ค๋ฉด Mongoose๊ฐ€ MongoDB ์„œ๋ฒ„์™€ "์—ฐ๊ฒฐ"๋˜์–ด ์žˆ์–ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ.

์ˆ˜์ • ์ฝ”๋“œ

// MongoDB ์—ฐ๊ฒฐ
mongoose.connect('mongodb://mongo:27017/todos')
  .then(() => console.log('โœ… MongoDB ์—ฐ๊ฒฐ ์„ฑ๊ณต!'))
  .catch(err => console.error('โŒ MongoDB ์—ฐ๊ฒฐ ์‹คํŒจ:', err));

์ดํ›„ docker-compose up --build๋ฅผ ๋‹ค์‹œ ํ•˜๊ณ  localhost:3000/todos์— ์ ‘์†ํ•˜๋‹ˆ [] ์ด ๋–ด๋‹ค(์ •์ƒ์ถœ๋ ฅ)

๊ทธ๋‹ค์Œ VS Code REST Client ํ™•์žฅ์œผ๋กœ POST /todos ์š”์ฒญ๋„ ๋ณด๋‚ด๋ดค๋‹ค.

  • requests.http(REST Client๊ฐ€ ์ฝ์„ ์š”์ฒญ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ)์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ์ง„ํ–‰

์—ฌ๊ธฐ์—์„œ์˜ ๋ฌธ์ œ๋Š”

PUT๊ณผ DELETE๊ฐ€ ๋˜์ง€ ์•Š์Œ
ํ•˜๋‚˜์˜ .http ํŒŒ์ผ์— GET, POST, PUT , DELETE๋“ฑ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์—ฐ์Šต์šฉ์œผ๋กœ ๋‹ค ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค.
1. index.js์— PUT๊ณผ DELETE์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋„ฃ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ index.js์— PUT, DELETE ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

// โœ… ํ•  ์ผ ์ˆ˜์ • (PUT)
app.put('/todos/:id', async (req, res) => {
  const { id } = req.params;
  const updated = await Todo.findByIdAndUpdate(id, { text: req.body.text }, { new: true });
  if (updated) {
    res.json(updated);
  } else {
    res.status(404).json({ error: "ํ•  ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์–ด์š”!" });
  }
});

// โœ… ํ•  ์ผ ์‚ญ์ œ (DELETE)
app.delete('/todos/:id', async (req, res) => {
  const { id } = req.params;
  const deleted = await Todo.findByIdAndDelete(id);
  if (deleted) {
    res.json({ message: "์‚ญ์ œ ์™„๋ฃŒ!" });
  } else {
    res.status(404).json({ error: "์‚ญ์ œํ•  ํ•ญ๋ชฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์–ด์š”!" });
  }
});

์ˆ˜์ •ํ•œ ์ „์ฒด .http ์ฝ”๋“œ

### ์ „์ฒด ํ•  ์ผ ๋ชฉ๋ก ์กฐํšŒ
GET http://localhost:3000/todos
Accept: application/json

###

### ์ƒˆ๋กœ์šด ํ•  ์ผ ์ถ”๊ฐ€ (POST ์š”์ฒญ)
POST http://localhost:3000/todos
Content-Type: application/json

{
  "text": "Docker Compose ์‹ค์Šต ์ตœ๊ณ !"
}

###

### ํ•  ์ผ ์ˆ˜์ • (PUT ์š”์ฒญ)
PUT http://localhost:3000/todos/์—ฌ๊ธฐ์—_์‹ค์ œ_ID_์ž…๋ ฅ
Content-Type: application/json

{
  "text": "์ˆ˜์ •๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค!"
}

###

### ํ•  ์ผ ์‚ญ์ œ (DELETE ์š”์ฒญ)
DELETE http://localhost:3000/todos/์—ฌ๊ธฐ์—_์‹ค์ œ_ID_์ž…๋ ฅ
  1. requests.http์— ์‹ค์ œID๋ฅผ ํ‹€๋ฆฌ๊ฒŒ ์ž…๋ ฅํ•จ
    ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•œ ์˜ค๋ฅ˜.. ๋‹น์—ฐํžˆ ID๊ฐ€ ๋งž์•„์•ผ ๋„ฃ๋“  ์ง€์šฐ๋“  ํ• ๊ฒƒ์ด๋‹ค.

4. Github Actions + DockerHub ์ž๋™ํ™” ๊ตฌ์ถ• (CI/CD)

  • .github/workflows/docker.yml ์ž‘์„ฑ
  • GitHub์— push โ†’ Docker ์ด๋ฏธ์ง€ ์ž๋™ ๋นŒ๋“œ โ†’ DockerHub์— ์—…๋กœ๋“œ
  • GitHub Secrets์— DOCKER_USERNAME / DOCKER_PASSWORD ๋“ฑ๋ก

yml ์ฝ”๋“œ ์ค‘ ์ผ๋ถ€

run: docker build -t ${{ secrets.DOCKER_USERNAME }}/compose-todo-api ./backend
run: docker push ${{ secrets.DOCKER_USERNAME }}/compose-todo-api

๋‚œ DockerHub ๋กœ๊ทธ์ธ์—์„œ ์กฐ๊ธˆ ๋ง‰ํ˜”์—ˆ๋‹ค.
์‹ค์ˆ˜: GitHub Secrets์— DOCKER_NAME์ด๋ผ๊ณ  ์“ฐ๊ณ  yml์—” DOCKER_USERNAME์ด๋ผ๊ณ  ์“ฐ๊ธฐ(๋ถˆ์ผ์น˜)
์—ฌ๋Ÿฌ๋ฒˆ์˜ push ๋์— ์„ฑ๊ณต

์—ฌ๊ธฐ๊นŒ์ง€ํ•˜๋ฉด

GitHub์— ์ฝ”๋“œ ํ‘ธ์‹œ โ†’ ์ž๋™์œผ๋กœ ์ตœ์‹  Docker ์ด๋ฏธ์ง€๊ฐ€ ๋นŒ๋“œ๋จ
DockerHub์— latest ํƒœ๊ทธ๋กœ ์—…๋กœ๋“œ๋จ
์ด์ œ ๋ˆ„๊ตฌ๋‚˜ docker pull๋กœ ๋ฐ›์•„์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•จ!

์ด ๊ฐ€๋Šฅํ•˜๋‹ค

5. .env๋กœ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ถ„๋ฆฌ (.env ์ ์šฉ ์‹ค์Šต)

  • MongoDB ์ฃผ์†Œ์™€ ํฌํŠธ๋ฅผ .env ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ
  • process.env๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์—์„œ ์ฐธ์กฐ
    .env ์ฝ”๋“œ
PORT=3000
MONGO_URL=mongodb://mongo:27017/todos

6. AWS EC2 ์„œ๋ฒ„์— ๋ฐฐํฌ

  • EC2 Ubuntu ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ & SSH ์ ‘์†
  • Docker + Docker Compose ์„ค์น˜
  • GitHub์—์„œ ํ”„๋กœ์ ํŠธ ํด๋ก 
  • .env ์ž‘์„ฑ ํ›„ docker-compose up์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
  • ์™ธ๋ถ€ IP + 3000 ํฌํŠธ๋กœ API ํ™•์ธ

EC2 ์ธ์Šคํ„ด์Šค ๋งŒ๋“ค๊ธฐ

  1. https://aws.amazon.com ์ ‘์†
  2. EC2 > ์ธ์Šคํ„ด์Šค ์‹œ์ž‘
    ์šด์˜์ฒด์ œ: Ubuntu 22.04 LTS
    ์ธ์Šคํ„ด์Šค ์œ ํ˜•: t2.micro (ํ”„๋ฆฌ ํ‹ฐ์–ด)
    ํ‚คํŽ˜์–ด ์ƒ์„ฑ โ†’ .pem ํŒŒ์ผ ์ €์žฅ
    3.์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ํ›„, ๋ณด์•ˆ ๊ทธ๋ฃน ์„ค์ •์—์„œ ํฌํŠธ ์—ด๊ธฐ
    22๋ฒˆ: SSH
    3000๋ฒˆ: Node.js ์ ‘์†์šฉ (์ปค์Šคํ…€ TCP)
    27017๋ฒˆ: MongoDB ์ ‘์†์šฉ (์„ ํƒ)
chmod 400 ์ด๋ฆ„.pem
ssh -i ์ด๋ฆ„.pem ubuntu@ํผ๋ธ”๋ฆญIP์ฃผ์†Œ

wsl์—์„œ EC2๋กœ ์ ‘์†ํ• ๋•Œ

์ด๋Ÿฐ ๋ฌธ๊ตฌ๊ฐ€ ๋– ์„œ ์ข€ ๋‹นํ™ฉํ–ˆ๋Š”๋ฐ, ๋ฆฌ๋ˆ…์Šค๋‚˜ ๋งฅ์—๋‚˜ SSH์ ‘๊ทผ์‹œ ํ•ญ์ƒ ๋ฌผ์–ด๋ณด๋Š” ์งˆ๋ฌธ์ด๋ผ๊ณ  ํ•œ๋‹ค.

[Linux / Mac] SSH ์ ‘๊ทผ์‹œ "Are you sure you want to continue connecting (yes/no)?" ์—†์ด

์ด ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ํ•ด๊ฒฐํ–ˆ๋‹ค.

์ ‘์†์„ฑ๊ณต

๊ทธ๋‹ค์Œ ๋‹ค์šด๋ฐ›์€ .pem ํŒŒ์ผ์„ wsl๋กœ ์˜ฎ๊ฒจ์ค˜์•ผํ•˜๋Š”๋ฐ,

explorer.exe .

์ด ๋ช…๋ น์–ด๋ฅผ wsl์— ์ž…๋ ฅํ•ด์„œ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์‹ ๊ธฐํ•˜๊ฒŒ ์œˆ๋„์šฐ์— ํŒŒ์ผ ๋ณต์‚ฌํ•˜๋“ฏ์ด ctrl+c ctrl+v๊ฐ€ ๊ฐ€๋Šฅํ–ˆ๋‹ค.

๋ฐฐํฌ ์™„๋ฃŒ

๋ญ”๊ฐ€ ์ด๋ ‡๊ฒŒ ๋œจ๋”๋‹ˆ

curl http://localhost:3000/todos

์„ ์ž…๋ ฅํ•˜๋‹ˆ [] ์ด ์ž˜ ๋‚˜์™”๋‹ค.

์†Œ๊ฐ:
์ด๋ฒˆ ์‹ค์Šต์„ ํ†ตํ•ด ๋‹จ์ˆœํ•œ API ์„œ๋ฒ„๋ฅผ ๋„˜์–ด์„œ ๋„์ปคํ™” โ†’ ์ž๋™ํ™” โ†’ ๋ฐฐํฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ์–ด ์ข‹์•˜๋‹ค. ์•„์ง๋„ ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๊ณ„์† ํ•˜๋‹ค๋ณด๋ฉด ์กฐ๊ธˆ์ด๋ผ๋„ ์•Œ๊ฒŒ๋˜์ง€ ์•Š์„๊นŒ.

profile
์ž‘๊ฒŒ, ๋น ๋ฅด๊ฒŒ, ์ง€์†๊ฐ€๋Šฅํ•˜๊ฒŒ

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