Docker ๋คํธ์ํน๊ณผ ๋ณผ๋ฅจ ์ ์
Docker ๋คํธ์ํน์ ์ปจํ ์ด๋๊ฐ ์๋ก, ๊ทธ๋ฆฌ๊ณ ํธ์คํธ ๋จธ์ ์ด๋ ์ธ๋ถ ๋คํธ์ํฌ์ ํต์ ํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ๋๋ค. Docker๋ ์ฌ๋ฌ ์ข ๋ฅ์ ๋ด์ฅ(built-in) ๋คํธ์ํฌ ๋๋ผ์ด๋ฒ๋ฅผ ์ ๊ณตํ๋ฉฐ, ๊ฐ์ฅ ํํ๊ฒ ์ฌ์ฉ๋๋ ๋๋ผ์ด๋ฒ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Docker ๋ณผ๋ฅจ์ ์ปจํ ์ด๋์ ๋ฐ์ดํฐ๋ฅผ ์๊ตฌ์ ์ผ๋ก ์ ์ฅํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ์ปจํ ์ด๋๋ ํ๋ฐ์ฑ(ephemeral)์ด๊ธฐ ๋๋ฌธ์ ์ปจํ ์ด๋๊ฐ ์ญ์ ๋๋ฉด ๊ทธ ์์ ์๋ ๋ฐ์ดํฐ๋ ํจ๊ป ์ฌ๋ผ์ง๋๋ค. ๋ณผ๋ฅจ์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์ปจํ ์ด๋์ ์๋ช ์ฃผ๊ธฐ์ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.

์ค๋ฅ ๋ฐ์

์ค๋ฅ ํด๊ฒฐ
docker-basic-demo/
โโโ api/
โ โโโ package.json
โ โโโ server.js
โ โโโ Dockerfile
โโโ database/
โโโ init.sql
# database/init.sql
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- Character set configuration
CREATE DATABASE IF NOT EXISTS simple_blog
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE simple_blog;
-- Table creation with utf8mb4
CREATE TABLE IF NOT EXISTS posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Sample data with Korean content
INSERT INTO posts (title, content) VALUES
('Docker ๋คํธ์ํน ๊ธฐ์ด', 'Docker ์ปจํ
์ด๋ ๊ฐ ํต์ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ด
์๋ค.'),
('๋ณผ๋ฅจ์ผ๋ก ๋ฐ์ดํฐ ์ ์งํ๊ธฐ', 'Docker ๋ณผ๋ฅจ์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๊ฐ ์๊ตฌ์ ์ผ๋ก ๋ณด์กด๋ฉ๋๋ค.'),
('์ฒซ ๋ฒ์งธ ์ค์ต', '๊ฐ๋จํ MySQL + Node.js ์ฐ๋ ์ค์ต์
๋๋ค.');
SELECT 'Database initialization completed' AS status;
Node.js ํ๋ก์ ํธ ์์ฑ
# api
npm init -y
api/package.json
{
"name": "simple-blog-api",
"version": "1.0.0",
"description": "๊ฐ๋จํ ๋ธ๋ก๊ทธ API",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.0",
"cors": "^2.8.5"
}
}
api/server.js
const express = require('express');
const mysql = require('mysql2/promise');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
// ๋ฏธ๋ค์จ์ด ์ค์
app.use(cors());
app.use(express.json());
// MySQL ์ฐ๊ฒฐ ์ค์
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'secret',
database: process.env.DB_NAME || 'simple_blog',
timezone: '+09:00',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: true
};
let connection;
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํจ์
async function connectDatabase() {
try {
connection = await mysql.createConnection(dbConfig);
console.log('โ
MySQL ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ฑ๊ณต');
console.log(`๐ ์ฐ๊ฒฐ ์ ๋ณด: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`);
} catch (error) {
console.error('โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์คํจ:', error.message);
console.log('๐ 5์ด ํ ์ฌ์๋...');
setTimeout(connectDatabase, 5000);
}
}
// ๋ผ์ฐํธ ์ ์
// 1. API ์ํ ํ์ธ
app.get('/health', async (req, res) => {
try {
const [rows] = await connection.execute('SELECT COUNT(*) as count FROM posts');
res.json({
status: 'OK',
message: 'API ์๋ฒ๊ฐ ์ ์ ๋์ํฉ๋๋ค',
timestamp: new Date().toISOString(),
database: 'connected',
posts_count: rows[0].count
});
} catch (error) {
res.status(500).json({
status: 'ERROR',
message: 'Database connection failed',
error: error.message
});
}
});
// 2. ๋ชจ๋ ๊ฒ์๊ธ ์กฐํ
app.get('/posts', async (req, res) => {
try {
const [rows] = await connection.execute(
'SELECT id, title, content, created_at FROM posts ORDER BY created_at DESC'
);
res.json({
success: true,
data: rows,
count: rows.length
});
} catch (error) {
console.error('๊ฒ์๊ธ ์กฐํ ์ค๋ฅ:', error);
res.status(500).json({
success: false,
message: '๊ฒ์๊ธ ์กฐํ์ ์คํจํ์ต๋๋ค',
error: error.message
});
}
});
// 3. ์ ๊ฒ์๊ธ ์์ฑ
app.post('/posts', async (req, res) => {
const { title, content } = req.body;
if (!title || !content) {
return res.status(400).json({
success: false,
message: '์ ๋ชฉ๊ณผ ๋ด์ฉ์ ํ์์
๋๋ค'
});
}
try {
const [result] = await connection.execute(
'INSERT INTO posts (title, content) VALUES (?, ?)',
[title, content]
);
const [newPost] = await connection.execute(
'SELECT id, title, content, created_at FROM posts WHERE id = ?',
[result.insertId]
);
res.status(201).json({
success: true,
message: '๊ฒ์๊ธ์ด ์์ฑ๋์์ต๋๋ค',
data: newPost[0]
});
} catch (error) {
console.error('๊ฒ์๊ธ ์์ฑ ์ค๋ฅ:', error);
res.status(500).json({
success: false,
message: '๊ฒ์๊ธ ์์ฑ์ ์คํจํ์ต๋๋ค',
error: error.message
});
}
});
// 4. ํน์ ๊ฒ์๊ธ ์กฐํ
app.get('/posts/:id', async (req, res) => {
const postId = req.params.id;
try {
const [rows] = await connection.execute(
'SELECT id, title, content, created_at FROM posts WHERE id = ?',
[postId]
);
if (rows.length === 0) {
return res.status(404).json({
success: false,
message: '๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค'
});
}
res.json({
success: true,
data: rows[0]
});
} catch (error) {
console.error('๊ฒ์๊ธ ์กฐํ ์ค๋ฅ:', error);
res.status(500).json({
success: false,
message: '๊ฒ์๊ธ ์กฐํ์ ์คํจํ์ต๋๋ค',
error: error.message
});
}
});
// 5. ๊ฒ์๊ธ ์ญ์
app.delete('/posts/:id', async (req, res) => {
const postId = req.params.id;
try {
const [result] = await connection.execute(
'DELETE FROM posts WHERE id = ?',
[postId]
);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค'
});
}
res.json({
success: true,
message: '๊ฒ์๊ธ์ด ์ญ์ ๋์์ต๋๋ค'
});
} catch (error) {
console.error('๊ฒ์๊ธ ์ญ์ ์ค๋ฅ:', error);
res.status(500).json({
success: false,
message: '๊ฒ์๊ธ ์ญ์ ์ ์คํจํ์ต๋๋ค',
error: error.message
});
}
});
// ์๋ฒ ์์
app.listen(PORT, async () => {
console.log(`๐ API ์๋ฒ๊ฐ ํฌํธ ${PORT}์์ ์คํ์ค์
๋๋ค`);
console.log(`๐ API ๋ฌธ์: http://localhost:${PORT}/health`);
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์์
await connectDatabase();
});
// ์ ์์ ์ธ ์ข
๋ฃ ์ฒ๋ฆฌ
process.on('SIGINT', async () => {
console.log('\n๐ ์๋ฒ๋ฅผ ์ข
๋ฃํฉ๋๋ค...');
if (connection) {
await connection.end();
console.log('โ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ข
๋ฃ');
}
process.exit(0);
});
api/Dockerfile
FROM node:18-alpine
# ์์
๋๋ ํ ๋ฆฌ ์ค์
WORKDIR /app
# package.json ๋ณต์ฌ ๋ฐ ์์กด์ฑ ์ค์น
COPY package*.json ./
RUN npm install
# ์์ค ์ฝ๋ ๋ณต์ฌ
COPY . .
# ํฌํธ ๋
ธ์ถ
EXPOSE 3000
# ์ ํ๋ฆฌ์ผ์ด์
์คํ
CMD ["npm", "start"]
๋จ๊ณ 1: MySQL ์ปจํ ์ด๋ ์คํ
# 1. MySQL ๋ณผ๋ฅจ ์์ฑ
docker volume create mysql-data
# 2. MySQL ์ปจํ
์ด๋ ์คํ (๋ณผ๋ฅจ ์ฐ๊ฒฐ)
docker run -d \
--name blog-mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=simple_blog \
-p 3306:3306 \
-v mysql-data:/var/lib/mysql \
-v $(PWD)/database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro \
mysql:8.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--default-time-zone='+09:00'
# 3. MySQL ์ปจํ
์ด๋ ๋ก๊ทธ ํ์ธ
docker logs blog-mysql
# 2. MySQL ์ปจํ
์ด๋ ์คํ (๋ณผ๋ฅจ ์ฐ๊ฒฐ) code
docker run -d --name blog-mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=simple_blog -p 3306:3306 -v mysql-data:/var/lib/mysql -v ${PWD}/database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-time-zone='+09:00'
๋จ๊ณ 2: API ์๋ฒ ๋น๋ ๋ฐ ์คํ
# 1. API ์๋ฒ ์ด๋ฏธ์ง ๋น๋
cd api
docker build -t simple-blog-api .
# 2. API ์๋ฒ ์ปจํ
์ด๋ ์คํ (MySQL๊ณผ ์ฐ๊ฒฐ)
docker run -d \
--name blog-api \
-e DB_HOST=blog-mysql \
-e DB_PASSWORD=secret \
-e DB_NAME=simple_blog \
-p 3000:3000 \
--link blog-mysql \
simple-blog-api
cd ..
๋จ๊ณ 3: ๋์ ํ์ธ
# 1. ์ปจํ
์ด๋ ์ํ ํ์ธ
docker ps
# 2. API ์ํ ํ์ธ
curl http://localhost:3000/health
# 3. ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ
curl http://localhost:3000/posts
# 4. ์ ๊ฒ์๊ธ ์์ฑ
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title":"Docker ์ค์ต", "content":"ํฌํธ ๋ฐ์ธ๋ฉ๊ณผ ๋ณผ๋ฅจ์ ๋ฐฐ์ ์ต๋๋ค!"}'
# 5. ๋ค์ ๊ฒ์๊ธ ๋ชฉ๋ก ํ์ธ
curl http://localhost:3000/posts