在 Windows 11 上使用 Docker 執行 MySQL 時,資料庫檔案的實際存放位置取決於你啟動容器的方式。
預設情況下,如果你沒有設定掛載點,Docker 會將資料存放於容器內部的虛擬檔案系統。在 Windows 的 Docker Desktop 環境中,這些資料實際上被封裝在名為 wsl.localhost/docker-desktop-data 的 WSL 2 虛擬磁碟檔案裡。這個檔案通常是一個 vhdx 格式的虛擬硬碟,位於使用者的 AppData 路徑下。這種方式的缺點是,一旦容器被刪除且沒有建立 Volume,資料就會隨之消失,且從 Windows 檔案總管很難直接存取或備份。
為了資料持久化與方便管理,開發者通常會使用兩種掛載方式。第一種是 Docker Volume,這是由 Docker 管理的儲存空間。在 WSL 2 模式下,這些資料會存放在 Linux 分發版的特定目錄內,路徑大約是 /var/lib/docker/volumes/。雖然這比虛擬磁碟容易管理,但依然不是直接存在 Windows 的資料夾中。
第二種是 Bind Mount,這是在執行 docker run 指令時,利用 -v 參數將 Windows 上的實體資料夾與容器內的 /var/lib/mysql 目錄對接。例如你指定 C:\mysql_data 作為掛載點,那麼所有的資料庫檔案、資料表與索引都會直接出現在這個 Windows 資料夾中。這種方式最直觀,你可以直接在 Windows 看到檔案,也方便進行手動備份或遷移。
總結來說,若不特別設定,資料會被藏在 WSL 2 的虛擬硬碟檔中。若有設定掛載,則會出現在你指定的 Windows 路徑或是 Docker 專用的 Linux 磁區內。
PostgreSQL 與 MySQL 在 Docker 上的處理邏輯幾乎完全相同。這兩者都遵循 Docker 的資料持久化機制,主要差異僅在於容器內部儲存資料的預設路徑不同。
當你在 Windows 11 執行 PostgreSQL 容器時,如果不指定掛載路徑,資料同樣會被存放在 WSL 2 的虛擬硬碟檔案中(通常是 vhdx 檔案)。這類資料庫映像檔的設計邏輯一致,都是將資料庫的實際檔案存放於容器內的某個特定目錄。對於 PostgreSQL 來說,這個內部路徑通常是 /var/lib/postgresql/data,而 MySQL 則是 /var/lib/mysql。
如果你希望在 Windows 檔案總管直接看到 PostgreSQL 的資料,處理方式與 MySQL 相同。你需要在執行 docker run 指令時,使用 -v 參數進行掛載。例如你可以將 Windows 上的 C:\pg_data 資料夾對接到容器內的 /var/lib/postgresql/data。這樣一來,無論是 PostgreSQL 產生的資料表檔案還是設定檔,都會直接出現在你指定的 Windows 資料夾中。
這種一致性是 Docker 的核心優勢。無論是 Redis、MongoDB 或 SQL Server 的容器版本,只要涉及到資料儲存,其核心概念都是透過 Volume 或 Bind Mount 將容器內的資料路徑映射到宿主機(也就是你的 Windows 環境)。這樣即使你刪除並重新建立容器,資料依然會保留在你的電腦硬碟上,不會隨著容器消失。
Docker Compose 配置示例
使用 Docker Compose 是管理多個資料庫最方便的方式。你可以在同一個專案資料夾下建立一個名為 docker-compose.yml 的檔案,內容如下:
YAML
services:
db-mysql:
image: mysql:latest
container_name: mysql-container
environment:
MYSQL_ROOT_PASSWORD: your_password
volumes:
- ./mysql_data:/var/lib/mysql
ports:
- "3306:3306"
db-postgres:
image: postgres:latest
container_name: postgres-container
environment:
POSTGRES_PASSWORD: your_password
volumes:
- ./postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
資料路徑的關鍵差異
雖然處理邏輯相同,但你必須留意容器內部的掛載路徑。MySQL 預設將資料放在 /var/lib/mysql,而 PostgreSQL 則是放在 /var/lib/postgresql/data。在上面的設定中,我使用了相對路徑(例如 ./mysql_data),這表示當你執行啟動指令後,Docker 會在你目前的資料夾下自動建立這兩個子資料夾,並把資料庫檔案寫入其中。
常用管理指令
在 Windows 終端機(PowerShell 或 CMD)進入該資料夾後,你可以使用以下指令:
- 啟動所有資料庫:docker compose up -d
- 停止並移除容器(但保留資料):docker compose down
- 查看容器執行狀態:docker compose ps
這種做法的好處是,所有的資料庫設定與檔案都集中在一個專案目錄內。即使你更換電腦或重灌 Docker,只要把這個資料夾整份備份走,下次在別處執行同樣的指令,資料庫就會原封不動地跑起來。
寫一段帶有掛載參數的 Docker 啟動指令
這裡分別為你準備 MySQL 與 PostgreSQL 的 Docker 啟動指令。這些指令使用了 -v 參數(Bind Mount),將 Windows 實體資料夾對接到容器內部,確保資料持久化。
MySQL 啟動指令
在執行前,請先在 Windows 上建立一個資料夾(例如 C:\docker_db\mysql),然後在 PowerShell 執行:
PowerShell
docker run -d `
--name mysql-server `
-e MYSQL_ROOT_PASSWORD=your_password `
-p 3306:3306 `
-v C:\docker_db\mysql:/var/lib/mysql `
mysql:latest
PostgreSQL 啟動指令
同樣地,先建立 Windows 資料夾(例如 C:\docker_db\postgres),再執行:
PowerShell
docker run -d `
--name postgres-server `
-e POSTGRES_PASSWORD=your_password `
-p 5432:5432 `
-v C:\docker_db\postgres:/var/lib/postgresql/data `
postgres:latest
指令參數說明
- -d:讓容器在背景執行,不會佔用目前的終端機視窗。
- –name:為你的容器取一個好記的名字,方便後續管理。
- -e:設定環境變數。對於資料庫映像檔,這通常用來設定初始的 root 或管理員密碼。
- -p:連接埠對接。格式為
主機連接埠:容器內部連接埠。這讓你可以在 Windows 使用資料庫管理軟體(如 DBeaver 或 Navicat)連線至 localhost。 - -v:掛載參數。這是最關鍵的部分,格式為
Windows路徑:容器內部路徑。
注意事項
在 Windows 上使用 Bind Mount 時,Docker 會詢問是否允許存取該資料夾,請務必點選允許。另外,建議掛載的 Windows 資料夾路徑不要包含空格或中文字元,以避免某些舊版本 Docker 出現編碼辨識問題。
將這些指令轉換成可重複使用的 Shell Script 檔案。
好的,這裡提供這兩個資料庫在 PowerShell 上的啟動指令。這些指令使用了 -v 參數,將 Windows 的實體路徑直接對接到容器內部。
MySQL 啟動指令
在執行之前,請先在 Windows 上手動建立一個資料夾(例如 C:\docker_db\mysql),然後執行:
PowerShell
docker run -d `
--name mysql-server `
-e MYSQL_ROOT_PASSWORD=your_password `
-p 3306:3306 `
-v C:\docker_db\mysql:/var/lib/mysql `
mysql:latest
PostgreSQL 啟動指令
同樣地,請先建立 Windows 資料夾(例如 C:\docker_db\postgres),再執行:
PowerShell
docker run -d `
--name postgres-server `
-e POSTGRES_PASSWORD=your_password `
-p 5432:5432 `
-v C:\docker_db\postgres:/var/lib/postgresql/data `
postgres:latest
指令關鍵說明
-d 代表在背景執行,不會佔住終端機。–name 是給容器取個好記的名字。-e 用來設定資料庫的初始密碼。-p 是把容器的埠號對接到你的 localhost。
最關鍵的是 -v 參數,格式為 Windows實體路徑:容器內部路徑。透過這個設定,資料庫產生的所有實體檔案都會直接存放在你指定的 Windows 資料夾內。即使你之後用 docker rm 刪除容器,只要這個資料夾還在,資料就不會遺失。
SQLite 的處理方式與 MySQL 或 PostgreSQL 有顯著不同,因為 SQLite 本質上只是一個單一的資料庫檔案(例如 data.db),而不是一個需要持續執行背景服務的資料庫引擎。
在 Docker 環境中使用 SQLite 時,通常是將該資料庫檔案掛載到執行應用程式(如 Node.js、Python 或 Go)的容器中。
SQLite 啟動掛載指令
假設你的應用程式程式碼在容器內的 /app 目錄,而你希望將 Windows 上的資料夾 C:\docker_db\sqlite 對接到容器,讓應用程式產生的資料庫檔案直接存在 Windows 硬碟上。你可以使用以下指令:
PowerShell
docker run -d `
--name my-app-with-sqlite `
-v C:\docker_db\sqlite:/app/data `
your-app-image:latest
關鍵差異與特性
MySQL 與 PostgreSQL 的掛載對象是整個資料夾,裡面包含複雜的系統檔案與日誌。而 SQLite 的掛載重點在於確保應用程式讀寫的是你主機上的那個 .db 檔案。
如果你的應用程式直接讀取特定檔案路徑,你甚至可以只掛載單一檔案而非整個資料夾,但通常建議掛載資料夾(如 /app/data),這樣 SQLite 產生的臨時日誌檔案(如 -wal 或 -shm 檔案)也能一併被持久化,避免資料損壞。
持久化優勢
將 SQLite 掛載到 Windows 實體路徑後,即使容器毀損或刪除,你依然可以使用 Windows 上的 SQLite 工具(如 SQLite Browser)直接打開該檔案進行檢視或修改。這對於開發階段的除錯非常有幫助,也讓備份變得極為簡單,只需要複製那個 .db 檔案即可。
寫一段 Node.js 連接掛載路徑下 SQLite 檔案的範例程式碼
這是一段簡單的 Node.js 範例,示範如何連接到掛載路徑下的 SQLite 資料庫檔案。這裡假設你已經按照前面的指令,將 Windows 的 C:\docker_db\sqlite 掛載到容器內的 /app/data 目錄。
Node.js 連接 SQLite 範例
你可以使用 sqlite3 或 better-sqlite3 套件來達成。以下使用較為常見的 sqlite3 作為範例:
JavaScript
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
// 指向容器內掛載的資料夾路徑
const dbPath = path.resolve(__dirname, 'data', 'database.sqlite');
// 建立或開啟資料庫連線
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('連線失敗:', err.message);
} else {
console.log('成功連線至掛載路徑下的 SQLite 資料庫');
}
});
// 建立一個簡單的資料表測試
db.serialize(() => {
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
db.run("INSERT INTO users (name) VALUES ('Test User')");
db.each("SELECT id, name FROM users", (err, row) => {
console.log(`用戶 ID: ${row.id}, 姓名: ${row.name}`);
});
});
db.close();
Dockerfile 配合設定
為了確保容器啟動時該目錄存在,你的 Dockerfile 可以這樣寫:
Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 確保建立資料掛載點目錄
RUN mkdir -p /app/data
CMD ["node", "index.js"]
執行建議
當你執行這個容器時,請務必加上 -v 參數。這樣一來,程式碼中對 /app/data/database.sqlite 的所有讀寫動作,實際上都會反應在 Windows 上的 C:\docker_db\sqlite\database.sqlite 檔案。
這樣做最大的好處是,即使你把 Docker 容器停掉,你仍然可以用 Windows 上的 SQLite 瀏覽器直接打開那個檔案檢查資料是否正確。
完整的 docker-compose.yml 整合版本
這是一份整合了 Node.js 應用程式與 SQLite 掛載設定的 docker-compose.yml 範本。這個配置會將你的程式碼與資料庫檔案完全分離,確保資料持久化。
專案結構建議
在你的專案目錄下,結構應該長這樣:
- project-root/
- docker-compose.yml
- Dockerfile
- index.js (你的 Node.js 程式碼)
- package.json
- data/ (掛載後會在此自動產生資料庫檔案)
docker-compose.yml 完整內容
YAML
version: '3.8'
services:
app:
build: .
container_name: node-sqlite-app
volumes:
# 將主機的 ./data 目錄掛載到容器內的 /app/data
- ./data:/app/data
environment:
- NODE_ENV=production
- DB_PATH=/app/data/database.sqlite
restart: always
搭配的 Dockerfile 內容
為了配合上述配置,你的 Dockerfile 需要確保工作目錄與權限正確:
Dockerfile
FROM node:18-slim
# 安裝 SQLite3 編譯時需要的基本工具
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production
COPY . .
# 建立資料存放目錄並確保權限
RUN mkdir -p /app/data
CMD ["node", "index.js"]
啟動與驗證
在終端機執行 docker compose up -d 啟動後,你可以觀察以下幾點:
- 實體檔案出現:你會看到專案目錄下的
data/資料夾內多出了一個database.sqlite(或你在程式中定義的檔名)。 - 直接讀取:你可以直接用 Windows 上的 SQLite 軟體打開這個
data/database.sqlite檔案,裡面就是容器內程式寫入的資料。 - 環境變數應用:在 Node.js 程式中,建議使用
process.env.DB_PATH來取得資料庫路徑,這樣之後如果要修改掛載位置,只需要改 compose 檔案即可。
如何透過環境變數在 Node.js 中動態切換開發與生產環境的資料庫路徑
好的,這裡提供一份完整的配置建議,讓你能透過環境變數動態切換本地開發與 Docker 容器內的資料庫路徑。
專案目錄結構
建議的目錄結構如下,這樣可以確保程式碼、設定與資料完全分離:
- project-root/
docker-compose.ymlDockerfileindex.js(主程式)package.json.env(環境變數設定檔)- data/ (此目錄會自動產生,存放 SQLite 檔案)
1. .env 環境設定
在 Windows 本地開發時,你可能想直接放在目前的 data 資料夾;在 Docker 內,則固定掛載到 /app/data。
Plaintext
# 本地開發建議設定
DB_PATH=./data/database.sqlite
PORT=3000
2. index.js 程式碼實作
使用環境變數來決定資料庫位置,增加彈性。
JavaScript
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
// 優先讀取環境變數,若無則預設在當前目錄的 data 資料夾
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data', 'database.sqlite');
console.log(`正在連線至資料庫位置: ${dbPath}`);
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('資料庫連線錯誤:', err.message);
return;
}
console.log('已成功連接 SQLite。');
});
// 初始化資料表
db.serialize(() => {
db.run("CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, msg TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP)");
db.run("INSERT INTO logs (msg) VALUES ('Container started')");
});
// 保持程序執行
setInterval(() => {
db.all("SELECT * FROM logs ORDER BY ts DESC LIMIT 1", (err, rows) => {
console.log('最新紀錄:', rows[0]);
});
}, 5000);
3. docker-compose.yml 整合配置
這份配置會覆蓋 .env 中的路徑,強制容器使用掛載的路徑。
YAML
services:
sqlite-app:
build: .
container_name: node-sqlite-service
volumes:
# 將 Windows 當前目錄下的 data 資料夾掛載到容器內的 /app/data
- ./data:/app/data
environment:
# 強制指定容器內的資料庫檔案路徑
- DB_PATH=/app/data/database.sqlite
restart: unless-stopped
如何驗證與操作
- 啟動服務:在專案根目錄執行
docker compose up -d。 - 檢查檔案:你會發現 Windows 的
data/資料夾出現了database.sqlite。 - 直接讀取:你可以直接用 Windows 上的 SQLite 軟體(如 DB Browser for SQLite)打開這個檔案。
- 刪除容器測試:執行
docker compose down移除容器後,再重新up啟動,你會發現之前的資料紀錄依然存在,這證明了 Bind Mount 運作正常。
Docker 權限設定的細節
在 Windows 11 上透過 Docker Desktop 使用 WSL 2 模式時,權限處理邏輯與純 Linux 環境有所不同。這主要是因為 Windows 檔案系統(NTFS)與 Linux 檔案系統(Ext4)之間的權限映射機制。
WSL 2 的權限映射機制
當你使用 -v 或 volumes 將 Windows 資料夾掛載到容器內時,WSL 2 會自動處理權限轉換。預設情況下,掛載進去的檔案在容器內部通常會顯示為 root 使用者所有,且權限通常是 777 或 755。這是為了確保 Windows 上的開發者可以直接編輯檔案,而容器內的服務也能讀寫。
常見的權限問題與解決方案
雖然自動映射很方便,但在某些嚴格檢查權限的資料庫(如 PostgreSQL)或需要特定 User ID (UID) 的應用中,可能會遇到問題。
- 指定 User 執行:如果你希望容器以特定身分執行,可以在
docker-compose.yml中加入user: "1000:1000"。但在 Windows 掛載目錄下,這有時會導致無法寫入,因為 Windows 檔案系統不完全支援 Linux 的 UID/GID 概念。 - 自動建立目錄的權限:如果掛載的 Windows 資料夾尚不存在,Docker 啟動時會以
root權限自動建立該目錄。這可能導致你在 Windows 上手動刪除該目錄時需要管理員權限。建議先在 Windows 手動建立好資料夾,再啟動 Docker。 - chmod 與 chown 的限制:在 Bind Mount(掛載 Windows 路徑)的情況下,你在容器內執行
chmod或chown通常不會產生實質影響,因為底層是 NTFS 檔案系統。如果你需要嚴格的 Linux 權限控制,建議使用 Named Volumes(由 Docker 管理的空間),而不是直接掛載 Windows 路徑。
實務建議:Named Volumes vs Bind Mounts
如果你對權限有嚴格要求,或者發現資料庫因為權限問題無法啟動,可以考慮切換成 Named Volumes:
YAML
services:
db:
image: postgres:latest
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: # 這會由 Docker 在 WSL 2 內部管理,支援完整的 Linux 權限
Windows 檔案共享設定
在較舊版本的 Docker Desktop 中,你需要在設定中手動勾選 File Sharing 來允許存取特定的 C 槽或 D 槽路徑。在目前的 WSL 2 模式下,這通常是自動開啟的,但如果遇到 Permission Denied,請檢查 Docker Desktop 設定中的 Resources > File Sharing 是否涵蓋了你的專案路徑。
總結來說,在 Windows 上開發時,Bind Mount(掛載實體路徑)最適合開發除錯,因為方便直接看檔案;而 Named Volume 最適合生產環境或對權限敏感的資料庫,因為效能更好且權限穩定。
調整之前的 docker-compose.yml 改用 Named Volumes 方式
這是一份將 SQLite 轉換為 Named Volume 方式的 docker-compose.yml 配置。
Named Volume 與之前的 Bind Mount(直接掛載 Windows 資料夾)最大的不同在於,資料會交由 Docker 在 WSL 2 的 Linux 檔案系統中管理,這能提供完整的 Linux 權限支援與更好的讀寫效能。
改用 Named Volume 的 docker-compose.yml
YAML
services:
sqlite-app:
build: .
container_name: node-sqlite-service
volumes:
# 使用具名磁碟卷掛載到容器內的 /app/data
- sqlite_storage:/app/data
environment:
- DB_PATH=/app/data/database.sqlite
restart: unless-stopped
volumes:
# 定義具名磁碟卷,由 Docker 統一管理
sqlite_storage:
Named Volume 的特性與權限優點
使用 Named Volume 處理權限時,有以下幾個關鍵細節:
- 自動初始化權限:當你啟動容器時,Docker 會自動將映像檔中
/app/data的權限與內容複製到sqlite_storage中。這解決了在 Windows 掛載時常遇到的Permission Denied問題,因為資料是在 Linux 原生檔案系統(Ext4)中運作。 - 效能優勢:在 Windows 11 上,Bind Mount 需要跨越 Windows 與 Linux 檔案系統的轉換層,讀寫頻繁時效能較差。Named Volume 直接跑在 WSL 2 內部,這對於需要頻繁寫入日誌或進行大量查詢的 SQLite 來說,速度會明顯提升。
- 資料存放位置:這類資料不再直接顯示在你的 Windows 專案資料夾中。在 Windows 11 的 Docker Desktop 環境下,這些資料實體存放在:
\\wsl$\docker-desktop-data\data\docker\volumes\sqlite_storage\_data
如何在 Windows 存取 Named Volume 的資料?
雖然它不像 Bind Mount 那樣直觀,但你依然可以透過以下方式存取:
- 透過 WSL 路徑:在檔案總管網址列輸入
\\wsl$\docker-desktop-data即可進入查看(具體路徑可能因版本略有不同)。 - 使用 Docker 指令備份:你可以透過指令將資料從 Volume 導出來:
docker cp node-sqlite-service:/app/data/database.sqlite ./backup.sqlite
總結建議
- 開發階段:建議使用 Bind Mount (
- ./data:/app/data),因為你可以直接在 Windows 用 GUI 工具開啟資料庫進行除錯。 - 測試或正式環境:建議使用 Named Volume (
- sqlite_storage:/app/data),權限最穩定,效能也最優。
寫一段自動化備份 Named Volume 資料到 Windows 指定路徑的指令碼
既然決定改用 Named Volume,這裡提供一段簡單的 PowerShell 指令碼,讓你可以隨時把容器內部的 SQLite 檔案「撈」出來備份到 Windows 指定路徑。
1. 自動化備份指令 (PowerShell)
你可以將這段程式碼存成 backup_db.ps1,或是直接在終端機執行:
PowerShell
# 設定變數
$CONTAINER_NAME = "node-sqlite-service"
$DB_SOURCE_PATH = "/app/data/database.sqlite"
$BACKUP_DEST_PATH = "C:\backups\sqlite\database_$(Get-Date -Format 'yyyyMMdd_HHmm').sqlite"
# 建立備份資料夾(如果不存在)
New-Item -ItemType Directory -Force -Path (Split-Path $BACKUP_DEST_PATH)
# 從容器複製檔案到 Windows
docker cp "${CONTAINER_NAME}:${DB_SOURCE_PATH}" "$BACKUP_DEST_PATH"
Write-Host "備份完成!檔案路徑:$BACKUP_DEST_PATH" -ForegroundColor Green
2. 為何 Named Volume 需要這段備份?
在之前的 Bind Mount 模式下,你的檔案就在專案資料夾裡,備份很直觀。但在 Named Volume 模式下,資料被鎖在 Docker 的虛擬磁碟中。
使用 docker cp 的好處是:
- 不干擾執行:你不需要停止容器就能進行快照備份。
- 路徑彈性:你可以把檔案丟到任何 Windows 支援的路徑(如雲端同步資料夾或 NAS)。
- 權限隔離:備份出來的檔案會自動轉為 Windows 目前使用者的權限,方便你用 GUI 工具開啟。
3. 進階:從備份還原
如果哪天資料壞了,想把備份推回去,指令也異曲同工:
PowerShell
# 停止容器以確保資料一致性
docker stop node-sqlite-service
# 把 Windows 上的備份檔覆蓋回容器內的路徑
docker cp "C:\backups\sqlite\database_backup.sqlite" "node-sqlite-service:/app/data/database.sqlite"
# 重新啟動
docker start node-sqlite-service
4. 權限設定的最後提醒
在 Windows 11 上使用 Named Volume 時,雖然不需要處理 Linux 的 chown 或 chmod,但要注意:
- 多容器共享:如果你有多個容器同時讀寫同一個 Named Volume,請確保你的應用程式(如 Node.js)有處理好檔案鎖定(File Locking),特別是 SQLite 在多程序讀寫時容易發生
SQLITE_BUSY錯誤。
備份邏輯直接整合進 docker-compose.yml 的指令
要在 docker-compose.yml 中直接整合備份邏輯,最優雅的做法是利用 Docker 的 profiles 功能或是建立一個專門負責備份的輔助服務。
這樣你不需要額外寫 PowerShell 腳本,直接透過 docker compose 指令就能觸發備份任務。
1. 修改後的 docker-compose.yml
我在原本的配置中增加了一個 backup 服務,並設定為 manual 模式(透過 profiles 達成),這樣它平時不會自動啟動,只有在你下指令時才會執行。
YAML
services:
# 主應用程式
sqlite-app:
build: .
container_name: node-sqlite-service
volumes:
- sqlite_storage:/app/data
environment:
- DB_PATH=/app/data/database.sqlite
restart: unless-stopped
# 備份輔助服務
backup:
image: alpine # 使用輕量級的 Linux 映像檔
profiles: ["tools"] # 設定 profile,讓它平時不自動啟動
volumes:
- sqlite_storage:/source:ro # 以唯讀模式掛載資料來源
- ./backups:/dest # 掛載主機的備份資料夾
entrypoint: >
sh -c "cp /source/database.sqlite /dest/database_$(date +%Y%m%d_%H%M%S).sqlite && echo '備份成功!'"
volumes:
sqlite_storage:
2. 如何執行備份
當你想備份時,只需要在專案目錄下輸入這行指令:
PowerShell
docker compose run --rm backup
3. 邏輯說明與優點
- Profiles 隔離:使用
profiles: ["tools"]可以確保你平常執行docker compose up -d時,備份服務不會被啟動,避免佔用資源。 - 唯讀掛載 (ro):備份服務對
sqlite_storage採用:ro(read-only) 唯讀掛載,這能保證備份過程不會意外更動到正在運行的資料庫。 - 自動清理:使用
--rm參數代表備份任務執行完畢後,Docker 會自動刪除這個臨時的備份容器,保持系統乾淨。 - 跨平台通用:這套邏輯寫在 YAML 裡,無論是在 Windows、Mac 或 Linux 上執行,指令與行為完全一致。
4. 權限細節提醒
由於 backup 服務使用的是 Alpine Linux (root 權限),它在 Windows 的 ./backups 資料夾建立檔案時,通常不會有權限問題。但如果你發現檔案無法刪除,請確認該資料夾在 Windows 上的安全性設定。
備份任務能定時自動執行(例如每小時一次)
如果要將備份邏輯直接整合進 docker-compose.yml 並實現定時自動執行,最常見的做法是在 Compose 中加入一個輕量級的 Cron 服務。這個服務會持續在背景執行,並根據你設定的時間表(例如每小時或每天)自動執行備份。
1. 整合定時備份的 docker-compose.yml
這裡使用 alpine 映像檔內建的 crond 功能。你可以直接將以下內容蓋掉原本的檔案:
YAML
services:
# 主應用程式
sqlite-app:
build: .
container_name: node-sqlite-service
volumes:
- sqlite_storage:/app/data
environment:
- DB_PATH=/app/data/database.sqlite
restart: unless-stopped
# 定時備份服務
backup-cron:
image: alpine
container_name: sqlite-backup-cron
volumes:
- sqlite_storage:/source:ro
- ./backups:/dest
# 設定 Cron 排程 (例如每小時執行一次)
# 格式:分 時 日 月 週 指令
entrypoint: >
sh -c "echo '0 * * * * cp /source/database.sqlite /dest/database_$$(date +\%Y\%m\%d_\%H\%M\%S).sqlite' > /etc/periodic/hourly/backup && chmod +x /etc/periodic/hourly/backup && crond -f -l 2"
restart: unless-stopped
volumes:
sqlite_storage:
2. 指令與權限細節說明
- 雙錢字號 ($$):在
docker-compose.yml中,如果你要在指令中使用變數(如date),必須使用兩個$符號,否則 Compose 會誤以為那是主機的環境變數。 - crond -f -l 2:這會讓 Cron 服務在前台執行(防止容器結束),並將日誌輸出等級設為 2,方便你透過
docker logs觀察備份是否成功。 - 自動清理舊備份:如果你擔心硬碟被塞滿,可以修改
entrypoint指令,加入刪除 7 天前舊檔的邏輯:find /dest -name "*.sqlite" -mtime +7 -exec rm {} \;
3. Windows 上的實務操作
當你執行 docker compose up -d 後,這個備份服務就會安靜地在背景運作。
- 檢查備份日誌:執行
docker logs -f sqlite-backup-cron即可看到它是否有準時觸發。 - 手動觸發測試:如果你不想等一小時,可以直接進容器執行一次備份指令:
docker exec sqlite-backup-cron sh -c "cp /source/database.sqlite /dest/manual_test.sqlite"
4. 權限與檔案鎖定補充
雖然是在 Windows 11 的 Docker Desktop 環境,但因為備份服務使用的是 Alpine (Linux),它在寫入 ./backups 時會遵循 Linux 權限。在 Windows 檔案總管看這些檔案時,通常不會有問題。
唯一要注意的是,SQLite 在備份瞬間如果剛好有極大量的寫入動作,雖然 cp 指令通常能運作,但最保險的做法是讓 Node.js 應用程式在閒置時才進行大規模變動。
這套自動化備份方案讓你即使使用 Named Volume,也能享有跟 Bind Mount 一樣的資料可視性與安全性。
把「自動刪除舊檔」的完整邏輯寫成一個獨立的 shell 腳本放在容器裡
為了讓邏輯更清晰且易於維護,建議將備份與清理舊檔的邏輯寫成一個獨立的 backup.sh 腳本,並透過 Dockerfile 放入容器中。這樣 docker-compose.yml 會變得非常乾淨,且執行時更穩定。
1. 撰寫備份與清理腳本 (backup.sh)
在你的專案目錄下建立 backup.sh,內容如下:
Bash
#!/bin/sh
# 設定變數
SOURCE="/source/database.sqlite"
DEST_DIR="/dest"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$DEST_DIR/database_$TIMESTAMP.sqlite"
# 1. 執行備份
if [ -f "$SOURCE" ]; then
cp "$SOURCE" "$BACKUP_FILE"
echo "[$TIMESTAMP] 備份成功: $BACKUP_FILE"
else
echo "[$TIMESTAMP] 錯誤: 找不到來源檔案 $SOURCE"
exit 1
fi
# 2. 刪除超過 7 天的舊檔 (可自行調整天數)
echo "正在檢查並清理 7 天前的舊備份..."
find "$DEST_DIR" -name "database_*.sqlite" -mtime +7 -exec rm {} \; -print
echo "清理完成。"
2. 修改 Dockerfile 納入腳本
你需要確保這個腳本被複製進去,並且擁有執行權限。
Dockerfile
FROM alpine
# 安裝必要工具(如果需要的話,alpine 內建有 find 與 cp)
RUN apk add --no-cache tzdata
ENV TZ=Asia/Taipei
WORKDIR /scripts
COPY backup.sh .
RUN chmod +x /scripts/backup.sh
# 啟動 crond 並設定排程
RUN echo "0 * * * * /scripts/backup.sh" > /etc/periodic/hourly/backup
RUN chmod +x /etc/periodic/hourly/backup
CMD ["crond", "-f", "-l", "2"]
3. 精簡後的 docker-compose.yml
現在 docker-compose.yml 不需要寫一長串指令,只需定義掛載點即可。
YAML
services:
sqlite-app:
build: .
container_name: node-sqlite-service
volumes:
- sqlite_storage:/app/data
environment:
- DB_PATH=/app/data/database.sqlite
restart: unless-stopped
backup-cron:
build:
context: .
dockerfile: Dockerfile.backup # 建議將備份用的 Dockerfile 另取名
container_name: sqlite-backup-cron
volumes:
- sqlite_storage:/source:ro
- ./backups:/dest
restart: unless-stopped
volumes:
sqlite_storage:
運作原理與優勢
這套架構將「備份」與「清理」邏輯封裝在腳本中,有以下優點:
- 自動維護空間:
find ... -mtime +7 -exec rm {} \;會自動掃描/dest資料夾,刪除修改時間超過 7 天的.sqlite檔案。這能防止你的 Windows 硬碟空間被無限增加的備份檔塞滿。 - 時區正確:在 Dockerfile 中設定了
TZ=Asia/Taipei,確保檔名上的時間戳記與台灣時間一致,方便你對照。 - 手動測試更簡單:如果你想立刻執行一次,只需輸入
docker exec sqlite-backup-cron /scripts/backup.sh即可,不需要重啟容器。
目前的設定是每小時備份一次並保留七天,這對多數開發需求已經非常足夠。
把這套邏輯也應用到 MySQL 或 PostgreSQL 的環境
將這套「自動備份 + 舊檔清理」邏輯應用到 MySQL 或 PostgreSQL 時,最大的差別在於不能直接使用 cp 複製檔案。因為這些資料庫在運作時,檔案處於鎖定或寫入狀態,直接複製可能會導致資料損毀。
我們必須改用資料庫專用的傾印工具(如 mysqldump 或 pg_dump),這能確保備份資料的一致性。
1. MySQL 自動備份腳本 (mysql_backup.sh)
這個腳本會連線到 MySQL 容器執行備份,並自動刪除超過 7 天的舊檔。
Bash
#!/bin/sh
# 設定變數
DB_HOST="db-mysql" # 對應 docker-compose 中的服務名稱
DB_USER="root"
DB_PASS="your_password"
DEST_DIR="/dest"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$DEST_DIR/mysql_backup_$TIMESTAMP.sql"
# 1. 執行 mysqldump (使用 -h 指定主機)
mysqldump -h $DB_HOST -u$DB_USER -p$DB_PASS --all-databases > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] MySQL 備份成功: $BACKUP_FILE"
# 壓縮檔案以節省空間
gzip "$BACKUP_FILE"
else
echo "[$TIMESTAMP] MySQL 備份失敗"
exit 1
fi
# 2. 清理 7 天前的舊檔
find "$DEST_DIR" -name "mysql_backup_*.sql.gz" -mtime +7 -exec rm {} \;
2. PostgreSQL 自動備份腳本 (pg_backup.sh)
PostgreSQL 的邏輯類似,但使用的工具不同。
Bash
#!/bin/sh
# 設定變數
DB_HOST="db-postgres"
DB_USER="postgres"
export PGPASSWORD="your_password"
DEST_DIR="/dest"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$DEST_DIR/pg_backup_$TIMESTAMP.sql"
# 1. 執行 pg_dumpall
pg_dumpall -h $DB_HOST -U $DB_USER > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] PostgreSQL 備份成功: $BACKUP_FILE"
gzip "$BACKUP_FILE"
else
echo "[$TIMESTAMP] PostgreSQL 備份失敗"
exit 1
fi
# 2. 清理 7 天前的舊檔
find "$DEST_DIR" -name "pg_backup_*.sql.gz" -mtime +7 -exec rm {} \;
3. 整合的 docker-compose.yml 結構
在 Compose 中,備份容器需要與資料庫容器處於同一個網路(network),這樣才能透過服務名稱連線。
YAML
services:
db-mysql:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: your_password
volumes:
- mysql_data:/var/lib/mysql
backup-service:
build:
context: .
dockerfile: Dockerfile.backup
volumes:
- ./backups:/dest
depends_on:
- db-mysql
restart: unless-stopped
volumes:
mysql_data:
關鍵差異與注意事項
- 映像檔選擇:備份容器的 Dockerfile 需要安裝對應的用戶端工具。例如備份 MySQL 就需要
apk add mysql-client,備份 PostgreSQL 則需要apk add postgresql-client。 - 資料一致性:使用
mysqldump或pg_dumpall會在備份時建立一致性的快照,這比 SQLite 直接cp檔案安全得多,也不會影響資料庫運行。 - 網路連線:在 Docker Compose 中,備份腳本的
DB_HOST必須設定為資料庫的service名稱(例如db-mysql),Docker 會自動處理內部 IP 的轉向。 - 空間管理:由於 SQL 傾印檔通常比 SQLite 檔案大很多,腳本中加入了
gzip壓縮指令,這能顯著減少備份佔用的磁碟空間。
包含用戶端工具安裝指令的完整 Dockerfile.backup
這裡提供一個完整的 Dockerfile.backup 範例,它會根據你的需求安裝 MySQL 與 PostgreSQL 的用戶端工具,並設定好排程執行環境。
1. 完整的 Dockerfile.backup
這個 Dockerfile 使用 Alpine 作為基底,體積小且安全。它會安裝必要的資料庫工具、時區設定,並將備份腳本排入 Cron。
Dockerfile
FROM alpine:3.18
# 安裝 MySQL, PostgreSQL 用戶端工具與時區套件
RUN apk add --no-cache \
mysql-client \
postgresql-client \
tzdata \
gzip
# 設定台灣時區
ENV TZ=Asia/Taipei
WORKDIR /scripts
# 複製備份腳本 (請確保 backup.sh 與 Dockerfile 在同一目錄)
COPY backup.sh .
RUN chmod +x /scripts/backup.sh
# 建立備份存放目錄
RUN mkdir -p /dest
# 設定 Cron 排程:每小時執行一次 (0 * * * *)
# 將指令寫入 Alpine 的 periodic 目錄
RUN echo "0 * * * * /scripts/backup.sh > /proc/1/fd/1 2>&1" > /etc/periodic/hourly/db-backup
RUN chmod +x /etc/periodic/hourly/db-backup
# 啟動 crond 並保持在前台執行,日誌輸出至標準輸出
CMD ["crond", "-f", "-l", "2"]
2. 整合型的備份腳本 (backup.sh)
你可以將 MySQL 與 PostgreSQL 的備份邏輯寫在一起,或者根據你的資料庫類型選擇性使用。
Bash
#!/bin/sh
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DEST_DIR="/dest"
# --- MySQL 備份邏輯 (如有使用) ---
if [ ! -z "$MYSQL_HOST" ]; then
echo "開始備份 MySQL..."
mysqldump -h "$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" --all-databases | gzip > "$DEST_DIR/mysql_$TIMESTAMP.sql.gz"
fi
# --- PostgreSQL 備份邏輯 (如有使用) ---
if [ ! -z "$PGHOST" ]; then
echo "開始備份 PostgreSQL..."
export PGPASSWORD="$PGPASSWORD"
pg_dumpall -h "$PGHOST" -U "$PGUSER" | gzip > "$DEST_DIR/pg_$TIMESTAMP.sql.gz"
fi
# --- 清理 7 天前的舊檔 ---
echo "清理舊備份檔案..."
find "$DEST_DIR" -name "*.sql.gz" -mtime +7 -exec rm {} \;
echo "[$TIMESTAMP] 備份與清理任務完成。"
3. 如何在 docker-compose.yml 中配置
在 Compose 檔案中,你只需要透過環境變數傳入連線資訊即可。
YAML
services:
db-mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root_password
volumes:
- mysql_data:/var/lib/mysql
backup-service:
build:
context: .
dockerfile: Dockerfile.backup
environment:
- MYSQL_HOST=db-mysql
- MYSQL_USER=root
- MYSQL_PASSWORD=root_password
# 如果有 PG 也可以一併加入
# - PGHOST=db-postgres
# - PGUSER=postgres
# - PGPASSWORD=pg_password
volumes:
- ./backups:/dest
depends_on:
- db-mysql
restart: unless-stopped
volumes:
mysql_data:
關鍵細節與運作方式
- 環境變數驅動:腳本內使用
if [ ! -z "$VARIABLE" ]來判斷。這表示你只要在 Compose 裡有設定該資料庫的環境變數,它就會自動執行該類型的備份。 - 日誌觀察:在
crond指令中加入了> /proc/1/fd/1,這能讓你在 Windows 終端機執行docker logs -f backup-service時,直接看到 Cron 執行的輸出結果。 - 安全性:使用
gzip壓縮後的 SQL 檔通常只有原始大小的 10% 到 20%,對於每天多次備份的場景非常節省空間。
寫一段自動恢復(Restore)的指令範例,以備不時之需
這裡提供一套針對 MySQL 與 PostgreSQL 的自動恢復(Restore)指令範例。當你需要從備份檔案還原資料時,可以使用這些指令將 .sql.gz 檔案重新匯入容器中。
1. MySQL 恢復指令
假設你的備份檔案名為 mysql_20260322_220000.sql.gz,且存放在 Windows 的 backups 資料夾內。
PowerShell
# 1. 解壓縮備份檔(在 Windows 或容器內皆可,這裡示範直接透過管道匯入)
# 2. 使用 docker exec 將資料流導回 mysql 容器
zcat .\backups\mysql_20260322_220000.sql.gz | docker exec -i db-mysql /usr/bin/mysql -u root -p'your_password'
2. PostgreSQL 恢復指令
PostgreSQL 的還原邏輯相似,建議使用 psql 工具來執行匯入。
PowerShell
# 1. 使用 zcat 解壓並透過管道傳送
# 2. 使用 docker exec 執行 psql
zcat .\backups\pg_20260322_220000.sql.gz | docker exec -i db-postgres psql -U postgres
3. SQLite 恢復指令
SQLite 最簡單,因為它只是個檔案。你只需要停止容器,覆蓋檔案後重啟即可。
PowerShell
# 1. 停止應用程式容器
docker stop node-sqlite-service
# 2. 將備份檔複製並覆蓋到掛載的路徑(假設你使用 Named Volume,可用 docker cp)
docker cp .\backups\database_20260322_220000.sqlite node-sqlite-service:/app/data/database.sqlite
# 3. 重新啟動容器
docker start node-sqlite-service
關鍵注意事項
- 資料覆蓋風險:恢復指令會根據 SQL 檔內的指令執行(通常包含
DROP TABLE或INSERT),這會覆蓋目前的資料。執行前建議先對目前的資料庫手動執行一次備份。 - 連線參數:在
docker exec指令中,-i(interactive) 是必須的,這樣才能讓解壓縮後的資料流順利進入容器的標準輸入。 - 版本相容性:還原時,建議目標容器的資料庫版本與備份時的版本一致(例如都是 MySQL 8.0),以避免語法不相容的問題。
總結
現在你已經擁有一套完整的 Docker 資料庫管理方案:
- 執行環境:透過
docker-compose.yml啟動。 - 資料持久化:使用 Named Volume 或 Bind Mount。
- 自動備份:透過
backup-service每小時定時產出壓縮檔。 - 空間維護:自動清理 7 天前的舊備份。
- 災難復原:透過
zcat與docker exec快速還原。
這套架構不論是在你目前的 Windows 11 開發環境,或是未來遷移到雲端 VPS,都能直接延用。
把這些恢復指令也寫成一個簡單的 restore.sh 腳本放入工具箱
為了方便你隨時執行還原任務,我們將「解壓與匯入」的邏輯寫成 restore.sh 腳本。這份腳本會列出所有備份檔案供你選擇,讓你不用手動輸入冗長的檔名。
1. 撰寫還原腳本 (restore.sh)
這個腳本會讀取 /dest 目錄下的所有壓縮檔,讓你透過編號選擇要還原哪一份。
Bash
#!/bin/sh
DEST_DIR="/dest"
echo "=== 資料庫還原工具 ==="
echo "可用備份清單:"
ls -1 $DEST_DIR/*.sql.gz | nl
echo -n "請輸入要還原的備份編號: "
read num
# 取得選定的檔案路徑
SELECTED_FILE=$(ls -1 $DEST_DIR/*.sql.gz | sed -n "${num}p")
if [ -z "$SELECTED_FILE" ]; then
echo "錯誤:無效的編號"
exit 1
fi
echo "正在準備還原檔案: $SELECTED_FILE"
# 判斷是 MySQL 還是 PostgreSQL (根據檔名關鍵字)
case "$SELECTED_FILE" in
*mysql*)
echo "偵測為 MySQL 備份,正在還原至 $MYSQL_HOST..."
zcat "$SELECTED_FILE" | mysql -h "$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD"
;;
*pg*)
echo "偵測為 PostgreSQL 備份,正在還原至 $PGHOST..."
export PGPASSWORD="$PGPASSWORD"
zcat "$SELECTED_FILE" | psql -h "$PGHOST" -U "$PGUSER"
;;
*)
echo "錯誤:無法辨識的備份格式"
exit 1
;;
esac
if [ $? -eq 0 ]; then
echo "還原成功!"
else
echo "還原過程中發生錯誤。"
fi
2. 更新 Dockerfile.backup
將還原腳本也放入備份容器中,這樣你的備份工具箱就完整了。
Dockerfile
# 在原本的 Dockerfile.backup 加入這行
COPY restore.sh .
RUN chmod +x /scripts/restore.sh
3. 如何執行還原
當你需要還原資料時,直接進入該備份容器執行腳本即可:
PowerShell
docker exec -it sqlite-backup-cron /scripts/restore.sh
運作邏輯說明
這套工具的核心優勢在於環境一致性。因為 restore.sh 跑在與 backup.sh 相同的容器內,它擁有完整的資料庫用戶端工具(mysql-client, postgresql-client),且與資料庫服務處於同一個 Docker 網路。
這代表你不需要在自己的 Windows 主機安裝任何資料庫軟體,就能完成備份與還原的所有維護工作。
總結建議
- 安全性:這套腳本會直接覆蓋目標資料庫的現有資料,執行前請務必確認選定的備份檔日期。
- SQLite 提醒:由於 SQLite 是直接操作實體檔案,還原時建議維持手動覆蓋檔案的方式(如前述的
docker cp),這樣最穩定。