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 νμ΅μ μλ£νμ΅λλ€. λ€μ λ¨κ³λ‘:
- μ€μ΅: μμ μ νλ‘μ νΈλ₯Ό Dockerν ν΄λ³΄κΈ°
- CI/CD: GitHub Actionsμ Docker μ°λ
- μ€μΌμ€νΈλ μ΄μ : Kubernetes κΈ°μ΄ νμ΅
- 보μ: Docker 보μ λ² μ€νΈ νλν°μ€
μΆκ° νμ΅ μλ£¶
- Docker 곡μ λ¬Έμ
- Docker Hub
- Play with Docker - λΈλΌμ°μ μμ Docker μ€μ΅