소프트웨어 개발/Docker

🐳 Docker 강의 10강 1편: 실전 프로젝트 (개발 환경) - Docker Compose로 풀스택 앱 구성

브라더댄 2025. 1. 27. 01:17
728x90
반응형
SMALL

내용이 길어 10-110-2 두 개의 포스팅으로 나누어 설명합니다. 이번 강의에서는 실제로 풀스택 애플리케이션(백엔드, 프론트엔드, DB) 환경을 Docker를 통해 **개발용(로컬)**과 **프로덕션용(운영)**으로 구축해 봅니다.

10-1: Docker Compose 기반 로컬 개발 환경 구성
10-2: Swarm/Kubernetes 기반 프로덕션 환경 배포

🚀 1. 프로젝트 개요

  • 목표: 간단한 SNS 형태의 풀스택 애플리케이션을 구성하여, Node.js(백엔드) + React(프론트엔드) + MySQL(데이터베이스) + **Redis(캐시)**로 이루어진 서비스를 Docker Compose 한 장으로 로컬에서 실행해 봅니다.
  • 개발 편의: 로컬에서 소스 코드를 수정하면 즉시 반영되도록 바인드 마운트를 활용할 예정입니다.

📝 2. 디렉토리 구조 예시

my-sns-project/
 ├─ backend/
 │   ├─ Dockerfile
 │   ├─ package.json
 │   ├─ app.js  (예: Express.js 진입점)
 │   └─ ...
 ├─ frontend/
 │   ├─ Dockerfile
 │   ├─ package.json
 │   ├─ src/
 │   └─ ...
 ├─ docker-compose.yml
 ├─ init.sql      (MySQL 초기 스크립트)
 └─ README.md
  • backend: Node.js + Express.js 기반 간단한 API 서버
  • frontend: React 기반 간단한 웹 프론트엔드
  • init.sql: MySQL 초기 테이블 생성 및 샘플 데이터 삽입 스크립트

Tip: 실제 개발 환경
보통 프론트엔드와 백엔드 저장소를 분리할 수도 있지만, 여기서는 실습 편의상 하나의 리포지토리에 묶어서 진행합니다.


👨‍💻 3. 백엔드(Node.js) 설정

3.1 예시 app.js (간단한 SNS API)

// backend/app.js
const express = require('express');
const redis = require('redis');
const mysql = require('mysql2/promise');

const app = express();
app.use(express.json());

// MySQL 연결
let db;
async function initDB() {
  db = await mysql.createConnection({
    host: 'db',   // docker-compose에서 정의한 서비스 이름
    user: 'root',
    password: 'secret',
    database: 'sns_db'
  });
}

// Redis 연결
const redisClient = redis.createClient({ url: 'redis://cache:6379' });
redisClient.on('error', (err) => console.error('Redis Error', err));

app.get('/api/posts', async (req, res) => {
  try {
    // 간단하게 DB 테이블에서 post 목록을 가져온다고 가정
    const [rows] = await db.execute('SELECT * FROM posts');
    res.json(rows);
  } catch (err) {
    res.status(500).json({ error: err.toString() });
  }
});

app.post('/api/posts', async (req, res) => {
  const { content } = req.body;
  try {
    await db.execute('INSERT INTO posts (content) VALUES (?)', [content]);
    res.json({ status: 'OK' });
  } catch (err) {
    res.status(500).json({ error: err.toString() });
  }
});

(async () => {
  await initDB();
  await redisClient.connect();
  app.listen(4000, () => {
    console.log('Backend listening on port 4000');
  });
})();

3.2 backend/Dockerfile

# 개발용 Dockerfile
FROM node:16

# 작업 디렉토리 생성
WORKDIR /usr/src/app

# package.json, package-lock.json 복사
COPY package*.json ./

# 의존성 설치
RUN npm install

# 소스 코드 복사 (개발 때는 bind mount를 권장)
COPY . .

EXPOSE 4000
CMD ["npm", "start"]

Tip: Bind Mount vs COPY
개발 환경에서는 바인드 마운트를 사용해 수정 시 자동 반영하는 것이 편리합니다. 프로덕션 빌드 시에는 COPY 방식을 사용해 이미지에 소스를 포함시킵니다.


🌐 4. 프론트엔드(React) 설정

4.1 간단한 React 예시

// frontend/src/App.js
import React, { useEffect, useState } from 'react';

function App() {
  const [posts, setPosts] = useState([]);
  const [content, setContent] = useState('');

  useEffect(() => {
    fetch('/api/posts')
      .then(res => res.json())
      .then(data => setPosts(data));
  }, []);

  const handleSubmit = async () => {
    await fetch('/api/posts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json'},
      body: JSON.stringify({ content })
    });
    setContent('');
  };

  return (
    <div>
      <h1>My SNS</h1>
      <div>
        <textarea value={content} onChange={e => setContent(e.target.value)} />
        <button onClick={handleSubmit}>Post</button>
      </div>
      <ul>
        {posts.map((p) => (
          <li key={p.id}>{p.content}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

4.2 frontend/Dockerfile

FROM node:16

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install

COPY . .
# 개발 시에는 npm start (개발 서버)
EXPOSE 3000
CMD ["npm", "start"]

Tip: 프록시 설정
React 개발 서버(npm start)는 로컬에서 3000번 포트로 구동됩니다. 백엔드(4000번 포트) 호출 시 CORS 문제를 피하려면 package.json에 "proxy": "http://backend:4000"와 같이 설정하거나, CORS 미들웨어를 백엔드에 추가합니다.


🏦 5. MySQL & Redis 설정

5.1 init.sql (MySQL 초기 스크립트)

CREATE DATABASE IF NOT EXISTS sns_db;
USE sns_db;

CREATE TABLE IF NOT EXISTS posts (
  id INT AUTO_INCREMENT PRIMARY KEY,
  content VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO posts (content) VALUES ('Hello Docker Compose!');

5.2 Redis

Redis는 별도의 초기화 스크립트 없이도 기본 이미지(redis:latest)로 충분합니다.


📦 6. docker-compose.yml (개발 환경)

version: "3.8"
services:
  backend:
    build: 
      context: ./backend
      dockerfile: Dockerfile
    container_name: my-backend
    ports:
      - "4000:4000"
    volumes:
      - ./backend:/usr/src/app  # 바인드 마운트로 소스 실시간 반영
    depends_on:
      - db
      - cache

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: my-frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/usr/src/app
    depends_on:
      - backend

  db:
    image: mysql:5.7
    container_name: my-db
    environment:
      MYSQL_ROOT_PASSWORD: secret
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

  cache:
    image: redis:latest
    container_name: my-redis
    ports:
      - "6379:6379"

volumes:
  mysql-data:
    driver: local

Tip: docker-entrypoint-initdb.d
MySQL 5.7 이미지는 /docker-entrypoint-initdb.d/*.sql에 존재하는 스크립트를 컨테이너 처음 실행 시 자동으로 실행합니다. 이를 통해 DB와 테이블 초기 설정을 자동화할 수 있습니다.


🏃 7. 실행 및 테스트

  1. 이미지 빌드 & 컨테이너 실행
    docker compose up -d
    
  2. 상태 확인
    docker compose ps
    
    • my-backend, my-frontend, my-db, my-redis가 모두 Up 상태여야 합니다.
  3. 접속 테스트
    • 프론트엔드: 브라우저에서 http://localhost:3000
    • 백엔드: curl http://localhost:4000/api/posts
    • DB: docker exec -it my-db mysql -u root -p (비밀번호 secret)
    • Redis: docker exec -it my-redis redis-cli

Tip: 코드 수정 반영
프론트엔드나 백엔드 소스 코드를 변경하면, 바인드 마운트 덕분에 컨테이너 내부에 실시간 반영됩니다.
단, Nodemon 등을 사용하지 않았다면 백엔드를 재시작해야 반영될 수 있으니, 필요하면 nodemon을 개발 의존성으로 추가하세요.


📝 정리

  • Docker Compose 하나로 풀스택 애플리케이션(백엔드, 프론트엔드, DB, 캐시)을 손쉽게 구동할 수 있습니다.
  • 개발 환경에서는 바인드 마운트를 통해 소스 수정 → 즉시 반영이 가능하며, MySQL 초기화 스크립트나 Redis 등도 간단히 설정할 수 있습니다.

다음(10-2) 예고

  • 프로덕션 환경으로 넘어가서, Swarm 또는 Kubernetes를 이용해 무중단 업데이트, 스케일링, 롤백 등을 실습해 봅니다.
  • CI/CD와 연계해 코드가 푸시될 때마다 자동으로 컨테이너 이미지를 빌드 & 배포하는 흐름도 짚어볼 예정입니다.

이제 로컬 개발환경은 완성! 다음 강의에서 실제 배포 전략을 고도화해 봅시다. 😊

728x90
반응형
LIST