Docker μ‹€μ „ 예제

Docker μ‹€μ „ 예제

이 λ¬Έμ„œμ—μ„œλŠ” μ‹€μ œ ν”„λ‘œμ νŠΈμ— Dockerλ₯Ό μ μš©ν•˜λŠ” 방법을 λ‹¨κ³„λ³„λ‘œ μ‹€μŠ΅ν•©λ‹ˆλ‹€.


예제 1: Node.js + Express + PostgreSQL

ν”„λ‘œμ νŠΈ ꡬ쑰

nodejs-postgres-app/
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ .env
β”œβ”€β”€ .dockerignore
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ package.json
β”‚   └── src/
β”‚       └── index.js
└── db/
    └── init.sql

파일 생성

backend/package.json:

{
  "name": "express-postgres-app",
  "version": "1.0.0",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "pg": "^8.11.3"
  }
}

backend/src/index.js:

const express = require('express');
const { Pool } = require('pg');

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

// PostgreSQL μ—°κ²°
const pool = new Pool({
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME || 'myapp',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || 'password'
});

// 라우트
app.get('/', (req, res) => {
  res.json({ message: 'Hello Docker!', status: 'running' });
});

app.get('/health', async (req, res) => {
  try {
    await pool.query('SELECT 1');
    res.json({ status: 'healthy', database: 'connected' });
  } catch (error) {
    res.status(500).json({ status: 'unhealthy', error: error.message });
  }
});

app.get('/users', async (req, res) => {
  try {
    const result = await pool.query('SELECT * FROM users ORDER BY id');
    res.json(result.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/users', async (req, res) => {
  const { name, email } = req.body;
  try {
    const result = await pool.query(
      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
      [name, email]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

backend/Dockerfile:

FROM node:18-alpine

WORKDIR /app

# μ˜μ‘΄μ„± μ„€μΉ˜ (μΊμ‹œ ν™œμš©)
COPY package*.json ./
RUN npm install --production

# μ†ŒμŠ€ μ½”λ“œ 볡사
COPY . .

# λΉ„λ£¨νŠΈ μ‚¬μš©μžλ‘œ μ‹€ν–‰
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 3000

CMD ["npm", "start"]

backend/.dockerignore:

node_modules
npm-debug.log
.git
.env
*.md

db/init.sql:

-- 초기 ν…Œμ΄λΈ” 생성
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- μƒ˜ν”Œ 데이터
INSERT INTO users (name, email) VALUES
    ('홍길동', 'hong@example.com'),
    ('κΉ€μ² μˆ˜', 'kim@example.com'),
    ('이영희', 'lee@example.com');

.env:

DB_PASSWORD=secretpassword123
DB_USER=appuser
DB_NAME=myapp

docker-compose.yml:

services:
  backend:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 5s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

volumes:
  pgdata:

μ‹€ν–‰ 및 ν…ŒμŠ€νŠΈ

# 디렉토리 생성 및 이동
mkdir -p nodejs-postgres-app/backend/src nodejs-postgres-app/db
cd nodejs-postgres-app

# (μœ„ νŒŒμΌλ“€ 생성 ν›„)

# μ‹€ν–‰
docker compose up -d

# μƒνƒœ 확인
docker compose ps

# 둜그 확인
docker compose logs -f backend

# API ν…ŒμŠ€νŠΈ
curl http://localhost:3000/
curl http://localhost:3000/health
curl http://localhost:3000/users

# μ‚¬μš©μž μΆ”κ°€
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "λ°•λ―Όμˆ˜", "email": "park@example.com"}'

# 정리
docker compose down -v

예제 2: React + Nginx (ν”„λ‘œλ•μ…˜ λΉŒλ“œ)

ν”„λ‘œμ νŠΈ ꡬ쑰

react-nginx-app/
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ nginx.conf
β”œβ”€β”€ package.json
β”œβ”€β”€ public/
β”‚   └── index.html
└── src/
    β”œβ”€β”€ App.js
    └── index.js

파일 생성

package.json:

{
  "name": "react-docker-app",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  "browserslist": {
    "production": [">0.2%", "not dead", "not op_mini all"],
    "development": ["last 1 chrome version"]
  }
}

public/index.html:

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>React Docker App</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

src/index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

src/App.js:

import React, { useState, useEffect } from 'react';

function App() {
  const [message, setMessage] = useState('Loading...');

  useEffect(() => {
    setMessage('Hello from React in Docker!');
  }, []);

  return (
    <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      height: '100vh',
      fontFamily: 'Arial, sans-serif'
    }}>
      <div style={{ textAlign: 'center' }}>
        <h1>{message}</h1>
        <p>이 앱은 Docker둜 λ°°ν¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.</p>
        <p>λΉŒλ“œ μ‹œκ°„: {new Date().toLocaleString()}</p>
      </div>
    </div>
  );
}

export default App;

Dockerfile (λ©€ν‹° μŠ€ν…Œμ΄μ§€ λΉŒλ“œ):

# Stage 1: λΉŒλ“œ
FROM node:18-alpine AS builder

WORKDIR /app

# μ˜μ‘΄μ„± μ„€μΉ˜
COPY package*.json ./
RUN npm install

# μ†ŒμŠ€ 볡사 및 λΉŒλ“œ
COPY . .
RUN npm run build

# Stage 2: Nginx둜 μ„œλΉ™
FROM nginx:alpine

# λΉŒλ“œ κ²°κ³Όλ¬Ό 볡사
COPY --from=builder /app/build /usr/share/nginx/html

# Nginx μ„€μ • 볡사
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

nginx.conf:

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # React Router 지원 (SPA)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 정적 파일 캐싱
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # gzip μ••μΆ•
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

docker-compose.yml:

services:
  frontend:
    build: .
    ports:
      - "80:80"
    restart: unless-stopped

μ‹€ν–‰

# λΉŒλ“œ 및 μ‹€ν–‰
docker compose up -d --build

# λΈŒλΌμš°μ €μ—μ„œ 확인
# http://localhost

# 정리
docker compose down

예제 3: 전체 μŠ€νƒ (React + Node.js + PostgreSQL + Redis)

ν”„λ‘œμ νŠΈ ꡬ쑰

fullstack-app/
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ docker-compose.dev.yml
β”œβ”€β”€ .env
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ nginx.conf
β”‚   └── (React ν”„λ‘œμ νŠΈ)
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── (Express ν”„λ‘œμ νŠΈ)
└── db/
    └── init.sql

docker-compose.yml:

services:
  # ν”„λ‘ νŠΈμ—”λ“œ
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    restart: unless-stopped

  # λ°±μ—”λ“œ API
  backend:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
      - DB_PORT=5432
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # PostgreSQL λ°μ΄ν„°λ² μ΄μŠ€
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redis μΊμ‹œ
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redisdata:/data
    restart: unless-stopped

volumes:
  pgdata:
  redisdata:

docker-compose.dev.yml (개발용 μ˜€λ²„λΌμ΄λ“œ):

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3001:3000"
    volumes:
      - ./frontend/src:/app/src
    environment:
      - REACT_APP_API_URL=http://localhost:3000

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    volumes:
      - ./backend/src:/app/src
    environment:
      - NODE_ENV=development
    command: npm run dev

  db:
    ports:
      - "5432:5432"

  redis:
    ports:
      - "6379:6379"

μ‹€ν–‰ λͺ…λ Ήμ–΄

# ν”„λ‘œλ•μ…˜
docker compose up -d

# 개발
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# νŠΉμ • μ„œλΉ„μŠ€ 둜그
docker compose logs -f backend

# λ°μ΄ν„°λ² μ΄μŠ€ 접속
docker compose exec db psql -U ${DB_USER} -d ${DB_NAME}

# Redis CLI
docker compose exec redis redis-cli

# 전체 정리
docker compose down -v

예제 4: WordPress + MySQL

docker-compose.yml

services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      - WORDPRESS_DB_HOST=db
      - WORDPRESS_DB_USER=wordpress
      - WORDPRESS_DB_PASSWORD=${DB_PASSWORD}
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress_data:/var/www/html
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: mysql:8
    environment:
      - MYSQL_DATABASE=wordpress
      - MYSQL_USER=wordpress
      - MYSQL_PASSWORD=${DB_PASSWORD}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    restart: unless-stopped

  # phpMyAdmin (선택사항)
  phpmyadmin:
    image: phpmyadmin:latest
    ports:
      - "8081:80"
    environment:
      - PMA_HOST=db
      - PMA_USER=wordpress
      - PMA_PASSWORD=${DB_PASSWORD}
    depends_on:
      - db

volumes:
  wordpress_data:
  db_data:

.env:

DB_PASSWORD=wordpresspass123
DB_ROOT_PASSWORD=rootpass123

μ‹€ν–‰

docker compose up -d

# WordPress: http://localhost:8080
# phpMyAdmin: http://localhost:8081

μœ μš©ν•œ λͺ…λ Ήμ–΄ λͺ¨μŒ

디버깅

# μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€ 접속
docker compose exec backend sh

# μ‹€μ‹œκ°„ 둜그 λͺ¨λ‹ˆν„°λ§
docker compose logs -f

# λ¦¬μ†ŒμŠ€ μ‚¬μš©λŸ‰ 확인
docker stats

# λ„€νŠΈμ›Œν¬ 확인
docker network ls
docker network inspect <network_name>

정리

# μ€‘μ§€λœ μ»¨ν…Œμ΄λ„ˆ μ‚­μ œ
docker container prune

# μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 이미지 μ‚­μ œ
docker image prune

# μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ³Όλ₯¨ μ‚­μ œ
docker volume prune

# 전체 정리 (주의!)
docker system prune -a --volumes

λ°±μ—…

# λ³Όλ₯¨ λ°±μ—…
docker run --rm \
  -v pgdata:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/pgdata-backup.tar.gz -C /data .

# λ³Όλ₯¨ 볡원
docker run --rm \
  -v pgdata:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/pgdata-backup.tar.gz -C /data

ν•™μŠ΅ μ™„λ£Œ!

Docker ν•™μŠ΅μ„ μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ λ‹¨κ³„λ‘œ:

  1. μ‹€μŠ΅: μžμ‹ μ˜ ν”„λ‘œμ νŠΈλ₯Ό Dockerν™” 해보기
  2. CI/CD: GitHub Actions와 Docker 연동
  3. μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜: Kubernetes 기초 ν•™μŠ΅
  4. λ³΄μ•ˆ: Docker λ³΄μ•ˆ 베슀트 ν”„λž™ν‹°μŠ€

μΆ”κ°€ ν•™μŠ΅ 자료

to navigate between lessons