Docker ComposeMySQLRedis開發環境DevOps前端教學

Docker Compose 教學:MySQL + Node.js + Redis 開發環境架設指南

多人開發環境不一致?用 Docker Compose 打造一致的 MySQL + Node.js + Redis 開發環境。一份設定檔讓團隊成員「一行指令」啟動完整環境。

· 6 分鐘閱讀

上一篇文章介紹了如何把一個 Node.js 服務包成 Docker 容器。但真實應用不會只有一個服務——你還需要資料庫、Redis、新手也不一定會裝的 elasticsearch 或 rabbitmq。Docker Compose 讓你用一個設定檔定義整個開發環境,無論是新進同事還是乾淨的機器,都能「一行指令」啟動完整的開發環境。


前言:為什麼需要 Docker Compose?

多人開發最大的痛苦之一:環境不一致

A 的機器跑了,B 的機器壞了,C 說「我這裡可以跑啊」。問題常常出在「我的機器有安裝 Redis,你的沒有」、「MySQL 版本不同,SQL 語法有差異」。

Docker Compose 的解決思路:用一個 docker-compose.yml 描述整個開發環境需要的服務,你的機器和我的機器看到的完全一樣

跟上一篇文章的 docker-compose.yml 相比,這篇要處理的更完整——不只是「一個 API + 一個資料庫」,而是多個基礎設施服務 + Node.js 應用的完整開發棧


完整開發環境的服務規劃

這次要建立的開發環境包含:

服務Image用途
app自己的 Node.js 專案API 應用
mysqlmysql:8關聯式資料庫
redisredis:7-alpine快取與 session 儲存
phpmyadminphpmyadmin:latest視覺化資料庫管理(可選)

撰寫 docker-compose.yml

# docker-compose.yml

services:
  # Node.js 應用
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      # 掛載 source code,修改時即時反映(熱重載)
      - ./src:/app/src
      # 不掛載 node_modules,讓容器內的獨立 node_modules 優先
    environment:
      - NODE_ENV=development
      - DATABASE_URL=mysql://admin:mysecretpassword@db:3306/myapp_dev
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy  # 等 db 健康檢查通過後才啟動
      cache:
        condition: service_started
    command: npm run dev  # 啟動 development 模式

  # MySQL 資料庫
  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: rootsecret
      MYSQL_DATABASE: myapp_dev
      MYSQL_USER: admin
      MYSQL_PASSWORD: mysecretpassword
    ports:
      - "3306:3306"
    volumes:
      # 資料持久化:主機目錄對應容器內的資料目錄
      - mysql_data:/var/lib/mysql
      # 初始化 SQL:第一次啟動時自動執行
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # Redis 快取
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      # Redis 資料持久化(RDB snapshot)
      - redis_data:/data
    command: redis-server --appendonly yes  # 開啟 AOF 持久化

  # phpMyAdmin(可選,視覺化管理 MySQL)
  phpmyadmin:
    image: phpmyadmin:latest
    ports:
      - "8080:80"
    environment:
      PMA_HOST: db
      PMA_USER: admin
      PMA_PASSWORD: mysecretpassword
    depends_on:
      - db
    profiles:
      - tools  # 用 --profile tools 才會啟動

volumes:
  mysql_data:
  redis_data:

初始化資料庫 schema

init.sql 放 Prisma 對應的 schema:

-- init.sql

CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  amount INT NOT NULL DEFAULT 0,
  status VARCHAR(50) NOT NULL DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

開發用的 Dockerfile

與生產環境的 Dockerfile 不同,開發用的版本需要保留 source code 掛載能力:

# Dockerfile.dev

FROM node:22-alpine

WORKDIR /app

# 開發模式:保留完整 node_modules,包含 devDependencies
COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000

# 開發時用 ts-node-dev 熱重載
CMD ["npm", "run", "dev"]

對比一下生產用的 Dockerfile(上一篇文章的多階段建構),開發版不需要多階段建構——因為我們需要 source code 完整存在容器裡以便 volume 掛載。


啟動與管理

# 啟動所有服務(除了 profiles: [tools] 的)
docker compose up -d

# 啟動並即時查看 logs
docker compose up

# 查看所有服務狀態
docker compose ps

# 查看特定服務的 logs
docker compose logs app
docker compose logs db

# 進入 MySQL 容器,直接操作資料庫
docker compose exec db mysql -u admin -pmysecretpassword myapp_dev

# 重啟某個服務
docker compose restart app

# 停止所有服務(但保留 volumes,資料不會消失)
docker compose down

# 停止並刪除 volumes(乾淨清除,資料庫資料會消失)
docker compose down -v

# 重新建構 Image(例如 package.json 改了)
docker compose up --build

# 使用 tools profile(啟動 phpMyAdmin)
docker compose --profile tools up -d

連接 Redis 的實際範例

// src/cache.ts
import { createClient } from 'redis';

const redisClient = createClient({
  url: process.env.REDIS_URL || 'redis://localhost:6379',
});

redisClient.on('error', (err) => console.error('Redis error:', err));
redisClient.on('connect', () => console.log('Redis connected'));

export async function getCachedUser(userId: number) {
  const key = `user:${userId}`;
  const cached = await redisClient.get(key);
  if (cached) {
    return JSON.parse(cached);
  }
  return null;
}

export async function setCachedUser(userId: number, user: object) {
  const key = `user:${userId}`;
  // 過期時間 5 分鐘
  await redisClient.setEx(key, 300, JSON.stringify(user));
}

在 Express route 裡使用:

// src/index.ts
app.get('/api/users/:id', async (req, res) => {
  const userId = parseInt(req.params.id);

  // 先查 Redis cache
  const cached = await getCachedUser(userId);
  if (cached) {
    return res.json({ source: 'cache', user: cached });
  }

  // Cache miss,查資料庫
  const user = await prisma.user.findUnique({ where: { id: userId } });
  if (!user) {
    return res.status(404).json({ error: '使用者不存在' });
  }

  // 寫入 cache
  await setCachedUser(userId, user);

  res.json({ source: 'db', user });
});

團隊協作要注意的事

1. 不要把 .env 提交到 Git

docker-compose.yml 裡密碼是明碼的(方便團隊共享),但 .env 應該加入 .gitignore,讓每個人自己建立:

# .env 範例(不要提交到 Git)
DATABASE_URL=mysql://admin:mysecretpassword@localhost:3306/myapp_dev
REDIS_URL=redis://localhost:6379

2. 為不同環境準備不同的 compose 檔

# 開發:啟動所有服務,掛載 source code
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# 生產:只有必要服務,Image 來自 registry
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

3. Windows 和 macOS 的檔案效能問題

Docker Desktop 在 Windows/macOS 上的 volume 掛載效能比 Linux 差很多。如果 npm install 在掛載目錄下執行很慢,改成在容器內執行一次 npm ci,把 node_modules 存在匿名 volume:

# 在 docker-compose.yml 的 app service 加入
volumes:
  - ./src:/app/src
  - /app/node_modules  # 匿名 volume,容器優先使用

常見問題

Q:docker compose 和 docker-compose 有什麼差別?

A:docker-compose 是舊版指令,V1 版本。docker compose(空白)是 Docker Desktop 內建的 V2 版本,指令相同但效能更好。建議用 docker compose(空白),它是 Docker 官方現在推薦的方式。

Q:MySQL 和 PostgreSQL 要選哪個?

A:如果是新專案,推薦 PostgreSQL。MySQL 的 SQL 語法與 PostgreSQL 有差異,且 PostgreSQL 的 JSON 支援、陣列型別、條件式索引更強大。但如果公司既有的服務是 MySQL,就別為了「潮流」而遷移。

Q:Redis 什麼時候該用?

A:Redis 的核心用途是快取短期資料儲存(如登入 session、API rate limiting、排行榜)。它不是主要資料庫,不適合當作持久化資料的儲存——Redis 的資料是可以丟失的(雖然有 RDB/AOF 持久化),要當持久化儲存請用 MySQL/PostgreSQL。

Q:多個服務的 log 怎麼看?

A:用 docker compose logs -f 可以同時看所有服務的 logs,加上 --tail=100 限制行數:

docker compose logs -f --tail=100

總結:一致性是團隊開發的基礎

Docker Compose 的核心價值:讓「在我機器上能跑」變成「在所有人的機器上都能跑」

這篇文章涵蓋的範圍:

  • 完整的 Docker Compose 設定(Node.js + MySQL + Redis + phpMyAdmin)
  • 開發環境與生產環境的分離策略
  • 資料庫初始化與 volume 持久化
  • Redis 快取實務範例
  • 團隊協作的注意事項

下一步:把這套開發環境分享給你的隊友,確認他們能在 clone 專案後 docker compose up -d 就能開始開發。這是 Docker Compose 價值的最好證明。


本篇是「前端工程師後端入門」系列第七篇。系列前六篇:《從 Node.js 原生 HTTP 到 Express 框架》、《RESTful API 設計:前端視角的 API 思維》、《Prisma ORM:前端工程師的第一堂資料庫課》、《Docker 部署入門:把你的應用打包上線》、《GitHub Actions CI/CD:自動化你的部署流程》、《SRE 基礎概念:監控、日誌、錯誤追蹤一次上手》。