Docker 入門
程式在開發的電腦上明明可以跑,但在其他電腦上跑不了。你有遇到過這情況嗎? 這是因為發環境與生產環境的不一致:作業系統版本不同、依賴的函式庫版本有差異,甚至是環境變數的設定不一樣,都會導致代碼在一個地方運行正常,在另一個地方卻頻頻出錯。
容器化 (Containerization) 技術則可以解決這問題,而 Docker 是較流行、廣泛使用的容器化技術。Docker 能夠將你的應用程式及其所有依賴打包成一個標準化的單元,稱為容器 (Container)。這個容器可以在任何支援 Docker 的機器上以完全相同的方式運行,解決了環境不一致的問題。
這篇文章將帶你從零開始,一步步了解 Docker 的核心概念,學習如何使用它的基本指令,並最終掌握如何透過 Dockerfile 和 Docker Compose 來管理你的應用程式。
📦 什麼是容器化 (Containerization)?
想像一下,你要搬家,你需要把書、廚具、衣服等所有東西都打包好。容器化就像是提供給你標準尺寸的箱子(容器),你把應用程式(例如一個網站)以及它運行所需的一切,包括代碼、運行環境 (runtime)、系統工具、函式庫等,全部都放進這個箱子裡。
這個箱子有幾個重要的特性:
- 標準化:無論你把這個箱子搬到哪部電腦上,只要那部電腦裝了 Docker,就能打開並運行裡面的東西,而且運行的結果會完全一樣。
- 隔離性:箱子是密封的。容器內的應用程式在一個被隔離的環境中運行,它看不到也無法存取主機 (host machine) 的檔案或用戶(除非你明確授權)。這意味著容器內的環境不會影響到主機,主機的環境也不會干擾容器。
這可以輕鬆地模擬生產環境。開發者可以在自己的電腦上,用與生產伺服器完全相同的容器環境來進行開發和測試。這樣一來,當代碼部署到生產伺服器時,就不會再出現因為環境差異而導致的意外行為了。
🤔 容器化 (Containerization) vs 虛擬化 (Virtualization)
很多人會將容器化與虛擬化混淆,雖然它們都旨在提供隔離的環境,但實現方式和效率卻大相徑庭。
- 虛擬化 (Virtualization):透過 Hypervisor 在主機作業系統 (Host OS) 之上,建立一個或多個完整的虛擬機器 (Virtual Machine, VM)。每個 VM 都包含自己的作業系統 (Guest OS)、函式庫和應用程式。這就像是在你的土地上蓋了幾棟獨立的房子,每棟房子都有自己的地基、水電系統。這種方式非常消耗資源,啟動也慢。
- 容器化 (Containerization):容器直接運行在主機的作業系統核心 (Kernel) 之上。它不需要模擬整個作業系統,而是共享主機的核心,只隔離應用程式所需的函式庫和依賴。這就像是在一棟公寓大樓裡,所有住戶共享大樓的基礎設施,但各自擁有獨立、安全的居住空間。容器因此非常輕量、啟動速度極快(通常是秒級)。
在 Windows 和 macOS 上使用 Docker: 值得注意的是,由於 Docker 容器技術源於 Linux 核心,在 Windows 或 macOS 上運行 Docker,需要一個虛擬化層。
- Windows: 你需要先安裝 WSL 2 (Windows Subsystem for Linux),它是一個輕量級的 VM,可以讓你運行一個完整的 Linux 核心。然後再安裝 Docker Desktop,它會利用 WSL 2 來運行 Docker 引擎。這也是為什麼在 Windows 上使用 Docker 可能會感覺比在 Linux 上慢一些的原因。
- Linux: 在 Linux 系統上,你只需要安裝 Docker Engine,不需要 Docker Desktop,容器會直接在主機核心上原生運行,性能最佳。

以上是 Windows 上執行 Docker Desktop 的介面,Docker 本身沒有圖形介面。本文重點介紹 Docker 命令。
🖼️ 映像檔 (Image) 與容器 (Container)
在 Docker 的世界裡,有兩個最核心的概念你必須理解:映像檔 (Image) 和容器 (Container)。
映像檔 (Image):可以把它想像成一個唯讀 (read-only) 的模板或藍圖。它包含了運行一個應用所需的所有東西:代碼、runtime、函式庫、環境變數和設定檔。映像檔是固定不變的,一旦建立就不會被更改。你可以從 Docker Hub(一個像 GitHub 的映像檔倉庫)下載別人已經做好的映像檔,例如
ubuntu
、nginx
或python
,也可以自己建立專屬的映像檔。容器 (Container):容器是映像檔的運行實例 (running instance)。當你運行一個映像檔時,Docker 會建立一個容器。容器是動態的、可讀寫的。
一個絕佳的比喻是物件導向程式設計 (OOP) 中的 Class 和 Object:
- 映像檔 (Image) 就好比是一個 Class。它是一個定義了所有屬性和方法的藍圖。
- 容器 (Container) 就好比是一個 Object。它是 Class 的一個具體實例,你可以從同一個 Class 建立出許多個獨立的 Object。
同樣地,你可以使用同一個映像檔,運行出許多個互不干擾的容器。
⌨️ 常用 Docker 指令
學會了概念,接下來就是動手實踐。以下是一些管理映像檔和容器最常用的指令。
指令 | 描述 | 範例 |
---|---|---|
docker pull | 從倉庫 (如 Docker Hub) 下載一個映像檔 | docker pull nginx:latest |
docker images | 列出本機上所有的映像檔 | docker images |
docker run | 從一個映像檔建立並啟動一個容器 | docker run -d -p 8080:80 nginx |
docker ps | 列出正在運行的容器 | docker ps |
docker ps -a | 列出所有容器 (包括已停止的) | docker ps -a |
docker stop | 停止一個正在運行的容器 | docker stop <container_id> |
docker start | 啟動一個已停止的容器 | docker start <container_id> |
docker rm | 移除一個或多個容器 | docker rm <container_id_1> <container_id_2> |
docker rmi | 移除一個或多個映像檔 | docker rmi <image_id> |
docker exec | 在一個正在運行的容器內執行指令 | docker exec -it <container_id> /bin/bash |
docker logs | 檢視一個容器的日誌 (logs) | docker logs <container_id> |
一個常見的陷阱:容器為何會立刻停止? 當你使用 docker run
時,容器會執行映像檔中預設的指令。如果該指令執行完畢,容器的生命週期也就結束了,它會自動停止。例如,docker run ubuntu echo "Hello World"
,容器會打印出 "Hello World" 然後立刻退出。
如果你希望容器持續在背景運行(例如一個網頁伺服器),你需要使用 -d
(detached mode) 參數:
# -d: 在背景運行容器
# -p 8080:80: 將主機的 8080 port 映射到容器的 80 port
docker run -d -p 8080:80 nginx
這樣,Nginx 伺服器就會在背景持續運行。你可以透過 docker ps
看到它。
如果你想進入容器進行互動式操作,可以使用 -it
(interactive tty) 參數:
# -it: 提供一個互動式的終端機
docker run -it ubuntu /bin/bash
這會讓你直接進入 Ubuntu 容器的 shell 環境。
🆔 映像檔 ID 與容器 ID 的使用
當你執行 docker ps
或 docker images
時,你會看到每個容器和映像檔都有一長串由數字和字母組成的唯一 ID。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f2a1b3c4d5e6 nginx:latest "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp eager_cray
好消息是,在執行指令時,你不需要輸入完整的 ID。你只需輸入 ID 的前幾個字元,只要這些字元足以在你的系統上唯一地標識該容器或映像檔即可。
例如,要停止上面那個 ID 為 f2a1b3c4d5e6
的容器,你通常只需要輸入:
docker stop f2a
Docker 會自動尋找以 f2a
開頭的容器並執行操作。這個技巧適用於所有需要 ID 的指令,如 docker rm
、docker rmi
、docker exec
等,大大提高了操作效率。
此外,你也可以使用 Docker 自動生成的容器名稱(如上例中的 eager_cray
)或者使用 docker run --name my-web-server
自己指定的名稱來操作容器,這通常比使用 ID 更直觀。
📝 Dockerfile:建立你的專屬映像檔
雖然你可以直接使用 Docker Hub 上的現成映像檔,但在多數情況下,你需要建立包含自己應用程式的自訂映像檔。這時候就需要 Dockerfile
。
Dockerfile
是一個純文字檔案,裡面包含了一系列的指令,Docker 會根據這些指令一步步地自動建立一個映像檔。這讓映像檔的建立過程變得透明、可重複。
以下是一些常用的 Dockerfile
指令:
指令 | 描述 | 範例 |
---|---|---|
FROM | 指定用來建立新映像檔的基礎映像檔 | FROM python:3.9-slim |
WORKDIR | 設定後續指令 (如 RUN , COPY , CMD ) 的工作目錄 | WORKDIR /app |
COPY | 將主機上的檔案或目錄複製到映像檔的檔案系統中 | COPY . . |
RUN | 在映像檔建置過程中執行指令,常用於安裝套件 | RUN pip install -r requirements.txt |
ENV | 設定環境變數 | ENV FLASK_APP=app.py |
EXPOSE | 聲明容器在運行時監聽的網絡端口 (僅為文件作用) | EXPOSE 5000 |
CMD | 提供容器啟動時的預設執行指令 | CMD ["flask", "run"] |
以下是一個簡單的 Dockerfile
範例,它會建立一個運行 Nginx 並提供自訂 HTML 頁面的映像檔。
首先,在你的專案目錄下建立一個 index.html
檔案:
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<body>
<h1>Hello from a docker container</h1>
</body>
</html>
然後,在同一個目錄下建立一個名為 Dockerfile
的檔案(沒有副檔名):
# Dockerfile
# 1. 使用官方的 Nginx Alpine 映像檔作為基礎
# Alpine Linux 是一個非常輕量的 Linux 發行版,適合用於容器
FROM nginx:alpine
# 2. 將主機上的 index.html 檔案複製到容器內的 Nginx 預設網站目錄
COPY ./index.html /usr/share/nginx/html/index.html
# 3. (可選) 聲明容器對外暴露的 port,這是一個文檔性質的指令
EXPOSE 80
有了 Dockerfile
後,就可以使用 docker build
指令來建立映像檔:
# -t: 為映像檔命名和標籤 (tag),格式為 <name>:<tag>
# .: 代表 Dockerfile 所在的當前目錄
docker build -t my-custom-nginx:1.0 .
建立成功後,你可以用 docker images
看到你剛剛建立的 my-custom-nginx:1.0
映像檔。現在,就可以像使用其他映像檔一樣運行它了:
docker run -d -p 8888:80 my-custom-nginx:1.0
現在打開瀏覽器訪問 http://localhost:8888
,你就會看到你的自訂 HTML 頁面了!
🚢 Docker Compose:管理多容器應用
當你的應用程式變得複雜,例如一個網站需要連接一個 MySQL 資料庫和一個 Redis 快取服務,這時你就需要同時管理多個容器。如果還用 docker run
來一個個啟動並設定它們之間的網絡連接,會非常繁瑣且容易出錯。
Docker Compose
就是為了解決這個問題而生的工具。它允許你使用一個 YAML 檔案 (docker-compose.yml
) 來定義和配置一個多容器的應用。
Dockerfile vs Docker Compose:
- Dockerfile: 專注於 建立單一映像檔。它是容器的「藍圖」。
- Docker Compose: 專注於 編排和運行多個容器。它是整個應用的「部署方案」。它會使用 Dockerfile 建立的映像檔(或直接從 Docker Hub 拉取)來啟動服務。
以下是一個 docker-compose.yml
的範例,它定義了兩個服務:一個 Nginx 網頁伺服器和一個 MySQL 資料庫。
在你的專案目錄下建立 docker-compose.yml
:
# docker-compose.yml
version: '3.8' # 指定 compose file 的版本
services:
# 定義第一個服務:網頁伺服器
web:
image: nginx:alpine
ports:
- "8080:80" # 將主機的 8080 port 映射到 web 容器的 80 port
volumes:
# 將主機的 ./html 目錄掛載到容器的 /usr/share/nginx/html
- ./html:/usr/share/nginx/html
# 定義第二個服務:資料庫
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: mysecretpassword # 設定 MySQL 的 root 密碼
MYSQL_DATABASE: myapp_db # 建立一個名為 myapp_db 的資料庫
volumes:
# 使用 docker volume 來持久化資料庫數據
- db_data:/var/lib/mysql
# 定義 volume,讓 Docker 來管理
volumes:
db_data:
在這個設定檔中:
services
下定義了web
和db
兩個服務。ports
用來做 port mapping。volumes
用來處理數據持久化。environment
用來設定容器內的環境變數。
要啟動這個應用,只需在 docker-compose.yml
所在的目錄下執行:
# -d 讓容器在背景運行
docker-compose up -d
Docker Compose 會自動幫你下載映像檔、建立並啟動 web
和 db
兩個容器,並設定好它們之間的網絡,讓 web
容器可以透過服務名稱 db
來訪問 MySQL 資料庫。
要停止並移除所有相關容器和網絡,只需執行:
docker-compose down
⚠️ 重要提醒:
docker-compose down
指令會停止並移除由up
指令建立的容器、網絡,以及未被外部指定的數據卷 (anonymous volumes)。這意味著容器內的非持久化數據將會完全遺失。這與docker-compose stop
非常不同,stop
僅僅是停止容器,容器實體和數據仍然存在,可以隨時用docker-compose start
重啟。除非你確定要清理整個環境,否則請謹慎使用down
。
以下是常用的 Docker Compose 指令:
指令 | 描述 | 範例 |
---|---|---|
up | 建立並啟動在 docker-compose.yml 中定義的服務 | docker-compose up -d |
down | 停止並移除容器、網絡和數據卷 | docker-compose down |
ps | 列出由 Compose 管理的容器及其狀態 | docker-compose ps |
logs | 檢視服務的日誌輸出 | docker-compose logs -f web |
exec | 在指定服務的容器內執行一個指令 | docker-compose exec db mysql -u root -p |
start | 啟動已存在的服務容器 | docker-compose start |
stop | 停止正在運行的服務容器,但不移除它們 | docker-compose stop |
build | 建立或重建服務的映像檔 | docker-compose build web |
💾 持久化儲存與掛載 (Volumes & Mounts)
容器本身是「短暫的」(ephemeral)。當你移除一個容器時,容器內部所做的任何檔案修改都會隨之消失。對於資料庫這類需要永久保存數據的應用來說,這顯然是不可接受的。
Docker 提供了兩種主要的方式來將數據持久化:
Volumes (數據卷):這是 官方推薦 的持久化數據方式。Volume 是由 Docker 管理的、存放在主機檔案系統某個特定位置的目錄。當你使用 volume 時,你不需要關心數據具體存在主機的哪個位置,Docker 會幫你處理好一切。Volume 的生命週期獨立於容器,即使容器被刪除,volume 裡的數據依然會被保留。在上面的
docker-compose.yml
範例中,db_data:/var/lib/mysql
就是一個 volume,它將 MySQL 的數據目錄/var/lib/mysql
持久化到名為db_data
的 volume 中。Bind Mounts (綁定掛載):這是將主機上的一個檔案或目錄直接掛載到容器中的指定路徑。這種方式非常適合在開發時使用。例如,你可以將你本地的代碼目錄掛載到容器中,這樣你在本地修改代碼後,無需重新建立映像檔,容器內就能立即看到變更。在範例中,
./html:/usr/share/nginx/html
就是一個 bind mount。
🔧 容器互動與管理
有時你需要進入正在運行的容器內部進行調試,或者執行一些臨時指令。
進入容器 Shell:使用
docker exec
指令。bash# -it 表示 interactive tty # my_container_name 是容器的名稱或 ID # /bin/bash 是要執行的指令 (打開一個 shell) docker exec -it my_container_name /bin/bash
將容器儲存為映像檔:如果你在一個容器內手動安裝了一些軟件或做了一些修改,並希望將這個狀態保存下來,可以使用
docker commit
。bashdocker commit <container_id> my-new-image:latest
但要注意,這種做法不夠透明和可追溯,最佳實踐仍然是透過修改
Dockerfile
並重新build
來產生新的映像檔。
✨ 總結
Docker 已經成為現代軟件開發和部署不可或缺的工具。它透過容器化技術,標準化了應用的打包和運行方式,解決「環境不一致」的難題。
通過這篇文章,你已經了解了 Docker 的核心概念,包括容器化、映像檔、容器,並學會了如何使用 Dockerfile
來建立自訂映像檔,以及如何利用 Docker Compose
來編排複雜的多容器應用。