本文作者是 AI 工程的初學者,正透過《AI Engineering from Scratch》課程自學,並全程搭配 AI 輔助:一步步照著課程開發者的進度動手執行,卡關時就向 AI 提問、請它引導。這篇文章,是把作者與 AI 互動的完整過程與學習歷程,先交由 AI 記錄、統整成初稿,再由作者親自逐段審視、修改、優化而成。AI 負責整理,最終判斷與文字由作者把關。
寫下它有兩個用意:替後來的讀者鋪一條能照著走的路徑,也幫作者自己複習、把學過的東西沉澱下來。
這篇怎麼讀
這篇跟系列其他文章一樣有三層:
- 作者說了什麼:課程的原始指令、步驟、練習
- 我問 AI 之後理解的背後道理:白話概念、類比、跟前面學過的東西怎麼接
- 走到這裡才會知道的事:課程沒明說但實際會遇到的補充
每一個指令,都要問到懂為止,才往下走。
這一課導覽框
- 課名:Phase 0 - Lesson 7 — Docker for AI
- 類型:Build(動手做的課)
- 目標:搞懂 Docker 為什麼存在、Image / Container / Dockerfile / Volume / Docker Compose 各是什麼、Dockerfile 怎麼寫、怎麼用 Docker Compose 跑多服務的 AI 應用
- 時間:課程標約 60 分鐘,我邊理解邊討論大概花了 90 分鐘
- Mac 使用者:Step 2(NVIDIA Container Toolkit)整段跳過,Mac 沒有 NVIDIA GPU。課程自己說了 macOS users can skip this。Dockerfile 的 base image 和 GPU 相關指令也用不到,但讀懂每一行在做什麼還是有價值
- 完課驗證:
docker --version+docker run hello-world跑過就算 Step 1 完成
這一課要解決的問題
課程開頭講了一個場景:
You trained a model on your laptop with PyTorch 2.3, CUDA 12.4, and Python 3.12. Your colleague has PyTorch 2.1, CUDA 11.8, and Python 3.10. Your model crashes on their machine. Your Dockerfile works on both.
你在自己的筆電上用 PyTorch 2.3、CUDA 12.4、Python 3.12 訓練了一個模型。你同事的電腦是 PyTorch 2.1、CUDA 11.8、Python 3.10。你的模型在他的機器上炸掉了。但你的 Dockerfile 在兩邊都能跑。
AI 專案的依賴組合特別複雜:Python 版本、PyTorch 版本、CUDA 驅動、cuDNN、系統層級的 C 函式庫,再加上 flash-attn 那種需要特定編譯器版本的套件。任何一個版本不對,整個東西就跑不起來。
Docker 把這些全部打包成一個 image,在哪裡跑都一樣。
先看懂作者的地圖
Docker 把你的程式碼、runtime、函式庫、系統工具包進一個隔離的單位叫做 container。課程說它像一台輕量的虛擬機器(VM),但跟真正的 VM 不同的是,container 不用跑自己的作業系統,它共用你電腦的 OS kernel,所以幾秒就能啟動。
為什麼 AI 專案比一般專案更需要 Docker
課程列了三個理由:
GPU 驅動很脆弱。 CUDA 12.4 的程式不能跑在 CUDA 11.8 上。Docker 可以把 CUDA toolkit 包在容器裡面,透過 NVIDIA Container Toolkit 跟外面的 GPU 驅動對接。我的 Mac 沒有 NVIDIA GPU,暫時不會碰到這個問題,但之後如果上雲端訓練模型就會遇到。
模型檔案很大。 一個 7B 參數的模型用 fp16 存就是 14 GB。容器裡的檔案,容器一關就沒了。如果每次重建容器都要重新下載 14 GB,不太實際。Docker 的 volume 讓你從外面掛一個資料夾進去,容器重建幾次都不用重新下載。
多服務架構很常見。 真正的 AI 應用不只是一支 Python 腳本。做 RAG 系統的時候,你需要推論伺服器、向量資料庫、也許還有前端。Docker Compose 一個指令把全部啟動。
五個關鍵字彙
課程給了一張表:
| 術語 | 意思 |
|---|---|
| Image | 唯讀的模板。食譜。從 Dockerfile 建出來的 |
| Container | Image 的一個執行中的實例。照著食譜開出來的那間廚房 |
| Dockerfile | 建 image 的指令,一層一層的 |
| Volume | 持久化的儲存空間,容器重啟後資料還在 |
| Docker Compose | 用 YAML 定義多容器應用的工具 |
三種常見的容器模式
課程提到 AI 工程常見三種用法:
| 模式 | 裡面有什麼 | 什麼時候用 |
|---|---|---|
| Dev Container | 完整工具包、Jupyter、編輯器支援、除錯工具 | 開發和實驗 |
| Training Container | 只有訓練腳本和必要依賴,精簡到不行 | 丟到 GPU 叢集上跑 |
| Inference Container | 最小 image、啟動最快 | 在 production 服務使用者請求 |
現階段不需要分這麼細,知道有這三種區分就好。
步驟 1:安裝 Docker
作者的指令:
# macOS
brew install --cask docker
open /Applications/Docker.app
# Ubuntu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group change to take effect
驗證:
docker --version
docker run hello-world
每行在做什麼
brew install --cask docker:用 Homebrew 安裝 Docker Desktop。--cask代表裝的是有圖形介面的 app,會出現在「應用程式」資料夾裡open /Applications/Docker.app:打開 Docker Desktop,讓背景服務跑起來docker --version:確認 Docker 裝好了docker run hello-world:從 Docker Hub 拉一個測試用的 image 下來跑,驗證整條流程通了
Docker 是裝在全域的
Docker 本身是系統級的工具,跟 git、node、uv 一樣,裝一次就全電腦都能用。跟你人在哪個資料夾無關。
Docker 這個「工具」是全域的,但它跑出來的每個 container 是隔離的。
docker run hello-world 做了什麼
跑完之後印出來的文字其實就是答案。Docker 的輸出列了四步:
- Docker client 聯繫了 Docker daemon(背景服務)
- Daemon 從 Docker Hub 拉了
hello-world這個 image 下來 - 從 image 建了一個 container,跑裡面的程式
- 程式的輸出傳回 terminal
docker run 這個指令拆開來看:docker run 是「從一個 image 建一個 container 然後跑它」,hello-world 是 image 的名字。
Docker Hub 就像 Python 的 PyPI,任何人都可以上傳 image,任何人都可以拉下來用。hello-world 是 Docker 官方準備好的測試用 image,專門拿來驗證安裝有沒有成功。它在 Docker Hub 上標著 “Docker Official Image”。裡面幾乎什麼都沒有,就只有一支小程式印出那段說明文字。
如果本機已經有這個 image,docker run 就直接用;如果沒有,它會自動去 Docker Hub 上拉。我第一次跑的時候有看到 Unable to find image 'hello-world:latest' locally 這行,就是在說「本機沒有,我去網路上抓」。
走到這裡才會知道的事:docker run 之前要先開 Docker Desktop
我裝完 Docker 之後直接跑 docker run hello-world,結果噴了一大段錯誤:
failed to connect to the docker API at unix:///var/run/docker.sock
原因是 Docker Desktop 還沒開。Docker 的指令(docker run、docker build 等等)需要一個背景服務(daemon)在跑,而 Mac 上這個 daemon 是由 Docker Desktop 管理的。先 open /Applications/Docker.app,等選單列出現鯨魚圖示,再跑指令就行了。
步驟 2:安裝 NVIDIA Container Toolkit(跳過)
課程原文:
This lets Docker containers access your GPU. macOS and Windows (WSL2) users can skip this; Docker Desktop handles GPU passthrough differently on those platforms.
這個工具讓 Docker 容器可以使用你的 GPU。macOS 和 Windows (WSL2) 的使用者可以跳過,Docker Desktop 在這些平台上用不同的方式處理 GPU。
我是 Mac,這步跳過。
步驟 3:理解 base images
寫 Dockerfile 的時候,第一行一定是 FROM,指定要從哪個 base image 開始蓋。作者的類比是「選地基」。
課程列了四種常見的 base image:
| Base Image | 內容 | 用途 | 大小 |
|---|---|---|---|
nvidia/cuda:12.4.1-devel-ubuntu22.04 | 完整 CUDA toolkit,含編譯器 | 需要編譯 GPU 套件時(flash-attn、bitsandbytes) | 約 4 GB |
nvidia/cuda:12.4.1-runtime-ubuntu22.04 | 只有 CUDA runtime,沒編譯器 | 跑已經編譯好的程式 | 約 1.5 GB |
pytorch/pytorch:2.3.1-cuda12.4-cudnn9-runtime | PyTorch 已經裝好在 CUDA 上面 | 省掉自己裝 PyTorch 的步驟 | 約 6 GB |
python:3.12-slim | 沒有 CUDA,純 CPU | CPU 推論、輕量工具 | 約 150 MB |
重點是:base image 決定了你的 image 有多大、裡面預先裝了什麼。選太大的浪費空間和時間,選太小的可能缺東西要自己補裝。前三個都跟 NVIDIA GPU 有關,我的 Mac 用不到,但理解它們的定位有助於讀懂別人的 Dockerfile。
步驟 4:讀懂一個 AI 用的 Dockerfile
課程在 code/Dockerfile 準備好了一個完整的範例。作者的意思是要你 build 它,但因為這個 Dockerfile 的 base image 是 nvidia/cuda,在 Mac 上 build 會下載好幾 GB 用不到的東西,所以我選擇讀懂每一行、不實際 build。
作者的指令:
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.12 \
python3.12-venv \
python3.12-dev \
python3-pip \
git \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1
RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel
RUN python -m pip install --no-cache-dir \
torch==2.3.1 \
torchvision==0.18.1 \
torchaudio==2.3.1 \
--index-url https://download.pytorch.org/whl/cu124
RUN python -m pip install --no-cache-dir \
numpy \
pandas \
scikit-learn \
matplotlib \
jupyter \
transformers \
datasets \
accelerate \
safetensors
WORKDIR /workspace
VOLUME ["/workspace", "/models"]
EXPOSE 8888
CMD ["python"]
Build 指令:
docker build -t ai-dev -f phases/00-setup-and-tooling/07-docker-for-ai/code/Dockerfile .
Run 指令:
docker run --rm -it --gpus all \
-v $(pwd):/workspace \
-v ~/models:/models \
ai-dev python -c "import torch; print(f'PyTorch {torch.__version__}, CUDA: {torch.cuda.is_available()}')"
在容器裡跑 Jupyter:
docker run --rm -it --gpus all \
-v $(pwd):/workspace \
-v ~/models:/models \
-p 8888:8888 \
ai-dev jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root
每行在做什麼
Dockerfile 的部分:
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04:選 base image,從一個裝好 CUDA 12.4 的 Ubuntu 系統開始蓋ENV DEBIAN_FRONTEND=noninteractive:設環境變數,安裝過程不跳互動式問題(像「你要選哪個時區?」)ENV PYTHONUNBUFFERED=1:讓 Python 的輸出不要被緩衝,直接印出來RUN apt-get update && apt-get install -y ...:用 Ubuntu 的套件管理器(apt)裝 Python 3.12 和基本工具。-y是自動回答 yes,--no-install-recommends是不裝推薦的額外套件(省空間)。最後rm -rf /var/lib/apt/lists/*清掉安裝快取,讓 image 小一點RUN update-alternatives --install ...:讓打python就是python3.12RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel:升級 pip。--no-cache-dir是不留下載快取,省空間RUN python -m pip install ... --index-url https://download.pytorch.org/whl/cu124:裝 PyTorch,指定 CUDA 12.4 的版本。--index-url告訴 pip 從 PyTorch 官方的 CUDA 12.4 套件庫下載,而不是從 PyPIRUN python -m pip install numpy pandas ...:裝其他常用的 AI 套件WORKDIR /workspace:設工作目錄,進 container 後預設在/workspaceVOLUME ["/workspace", "/models"]:宣告兩個 volume 掛載點EXPOSE 8888:告訴 Docker 這個 container 會用到 port 8888(Jupyter 的預設 port)。這行是聲明,不是實際打開 port,跑的時候還是要用-p才會真的映射CMD ["python"]:容器啟動後預設跑的指令
Build 指令的部分:
docker build:根據 Dockerfile 建一個 image-t ai-dev:幫這個 image 取名叫ai-dev(t 是 tag)-f ...:指定 Dockerfile 的路徑.:build context,把當前目錄的內容都給 Docker 看
Run 指令的部分:
docker run:從 image 建一個 container 並跑它--rm:跑完自動刪除這個 container(不留殘骸)-it:互動模式。-i是保持標準輸入開著,-t是分配一個終端機--gpus all:讓容器可以使用所有 GPU(Mac 不支援,要拿掉)-v $(pwd):/workspace:volume mount,把目前的資料夾掛到容器裡的/workspace-v ~/models:/models:把家目錄的 models 資料夾掛到容器裡的/models-p 8888:8888:port 映射,把容器裡的 8888 port 對應到外面的 8888
Dockerfile 裡面碰到三種語法
我問 AI:「這一課裡面碰到的語法都是 shell 嗎?」
答案是不全是。整理一下這課碰到的三種語言:
| 語言 | 出現在哪裡 | 例子 |
|---|---|---|
| Shell | terminal 裡打的指令 | docker build、docker run、docker compose up |
| Dockerfile 語法 | Dockerfile 裡面 | FROM、RUN、ENV、WORKDIR、CMD |
| YAML | docker-compose.yml | 用縮排組織設定的格式 |
Shell 指令跟之前學的一樣,在 terminal 打。Dockerfile 語法是 Docker 自己定義的,只有 Dockerfile 裡面認。但 RUN 後面接的內容(apt-get install、pip install)又是 shell 指令,因為 RUN 的意思就是「在容器裡面跑一行 shell」。YAML 則是一種設定檔格式,用來描述 Docker Compose 的服務配置。
我追問:「所以 AI 工程師要學會這麼多種語法?」AI 的回答讓我比較安心:不用每個都精通,但要能看懂、能改。寫的主力是 Python,其他的多數時候是看現成的模板、改幾行。
每個 RUN 是一個 layer
Dockerfile 裡每一行 RUN 會產生一層 layer。Docker build 的時候,如果某一層的指令沒改過,它會用 cache(快取),不重新執行。所以如果你只改了最後一行 RUN pip install,前面安裝 Python、安裝 PyTorch 的那些層都不用重跑。
這就是為什麼 Dockerfile 裡面會把「不常改的東西」放前面、「常改的東西」放後面。
步驟 5:Volume mounts(掛載資料夾)
作者的指令:
# 掛你的程式碼
-v $(pwd):/workspace
# 掛模型資料夾
-v ~/models:/models
# 掛資料集
-v ~/datasets:/data
在容器裡面讀取掛載的模型:
from transformers import AutoModel
model = AutoModel.from_pretrained("/models/llama-7b")
每行在做什麼
-v $(pwd):/workspace:冒號左邊是你電腦上的真實路徑($(pwd)是「目前的資料夾」),右邊是容器裡面看到的路徑-v ~/models:/models:把家目錄的models資料夾掛到容器的/models-v ~/datasets:/data:把datasets資料夾掛到容器的/data
容器裡面的東西會消失,Volume 不會
容器關掉重開,裡面的檔案就沒了。但 volume mount 是從外面接進來的資料夾,兩邊看到的是同一份檔案。容器關了,你電腦上的資料夾還在,下次再掛進去就好。
課程特別強調 volume 對 AI 工作的意義:模型檔案動輒十幾 GB,如果不掛載、每次重建容器都重新下載,時間和頻寬都浪費。
步驟 6:Docker Compose(多服務一起跑)
作者的指令:
services:
ai-dev:
build:
context: .
dockerfile: Dockerfile
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
- ../../../:/workspace
- ~/models:/models
- ~/datasets:/data
ports:
- "8888:8888"
stdin_open: true
tty: true
command: jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root
qdrant:
image: qdrant/qdrant:v1.12.5
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
volumes:
qdrant_data:
操作指令:
# 全部啟動(-d 是背景執行)
docker compose up -d
# 全部停掉
docker compose down
# 停掉並刪除 volume
docker compose down -v
在 AI 容器裡測試連線到 Qdrant:
from qdrant_client import QdrantClient
client = QdrantClient(host="qdrant", port=6333)
print(client.get_collections())
每行在做什麼
docker-compose.yml 的結構:
services:底下定義了兩個服務:ai-dev和qdrantai-dev是從 Dockerfile build 出來的,掛了程式碼、模型、資料集三個 volume,port 8888 映射出來,啟動後跑 Jupyterdeploy.resources.reservations.devices那段是 GPU 設定,Mac 用不到qdrant是直接從 Docker Hub 拉現成的 image(qdrant/qdrant:v1.12.5),不用自己 buildvolumes: qdrant_data:是宣告一個 named volume,讓 Qdrant 的資料持久化
操作指令:
docker compose up -d:啟動 yml 裡定義的所有服務。-d是 detach,背景跑docker compose down:停掉所有服務docker compose down -v:停掉所有服務,同時刪除 volume(Qdrant 儲存的資料也會清掉)
容器之間用服務名字互相找
Docker Compose 會自動建一個內部網路,兩個容器之間可以用服務名字互相找到。所以 Python 程式裡寫 host="qdrant",不是 IP 位址,是直接用 yml 裡定義的服務名稱。Docker Compose 幫你處理了 DNS。
這就是 Docker Compose 的價值:一個 YAML 檔案定義所有服務和它們之間的關係,一個指令全部啟動。做 RAG 系統的時候,AI 推論伺服器和向量資料庫要同時跑,手動一個一個啟動很麻煩,Compose 讓這件事變成一行指令。
步驟 7:常用 Docker 指令
作者的指令:
# 列出正在跑的 container
docker ps
# 列出所有 image 和大小
docker images
# 清掉沒在用的 image(釋放硬碟空間)
docker system prune -a
# 在跑著的 container 裡面看 GPU 狀態
docker exec -it <container_id> nvidia-smi
# 從 container 複製檔案到你的電腦
docker cp <container_id>:/workspace/results.csv ./results.csv
# 看 container 的 log
docker logs -f <container_id>
每行在做什麼
docker ps:列出正在跑的 container,包含 container ID、用哪個 image、什麼時候建的docker images:列出本機存了哪些 image、各佔多大docker system prune -a:清掉所有沒在用的 image 和 container。AI 的 image 動輒好幾 GB,這行很實用。-a是連沒有被任何 container 使用的 image 也清掉docker exec -it <container_id> nvidia-smi:在一個已經在跑的 container 裡面執行一個指令。exec跟run不一樣:run是建一個新的 container,exec是進到一個已經在跑的 container 裡面docker cp:在 container 和你的電腦之間複製檔案docker logs -f:看 container 的 log。-f是 follow,持續跟著看新的輸出
<container_id> 不用背,跑 docker ps 就會列出來。
Use It
課程的總結:
- Use
docker compose upto start your dev environment and vector database together- Mount your code, models, and data as volumes so nothing is lost between rebuilds
- When a lesson requires a new Python package, add it to the Dockerfile and rebuild
- Share your Dockerfile with teammates. They get the exact same environment.
- 用
docker compose up同時啟動開發環境和向量資料庫- 把程式碼、模型、資料集用 volume 掛進去,重建 container 不怕弄丟
- 某一課需要新的 Python 套件,加進 Dockerfile 然後重 build
- 把 Dockerfile 分享給隊友,他們拿到一模一樣的環境
沒有 GPU 的話,把 --gpus all 和 NVIDIA 的 deploy 區塊拿掉就好。PyTorch 偵測不到 CUDA,會自動 fallback 到 CPU。
四個練習
課程出了四題,都需要實際 build 和跑 container。因為 Dockerfile 的 base image 是 NVIDIA CUDA,在 Mac 上 build 意義不大,我先把題目和要驗證的東西記下來,之後有適合的環境再動手:
- Build Dockerfile,在 container 裡跑
python -c "import torch; print(torch.__version__)" - 啟動 docker-compose,驗證 Qdrant 在 AI container 裡能連到
http://qdrant:6333/collections - 在 Dockerfile 加
flask,重 build,跑一個簡單 API server 在 port 5000,用-p 5000:5000映射 - 用
docker images看 image 大小,把 base image 從devel換成runtime,比較大小差異
四個關鍵詞
| 術語 | 一般怎麼講 | 白話意思 |
|---|---|---|
| Container | ”Lightweight VM”(輕量虛擬機) | 一個隔離的 process,用的是宿主的 kernel,但有自己的檔案系統和網路 |
| Image layer | ”Cached step”(快取的步驟) | Dockerfile 的每行指令產生一層 layer。沒改過的 layer 會被快取,重 build 會快很多 |
| NVIDIA Container Toolkit | ”GPU in Docker” | 一個 runtime hook,透過 --gpus flag 讓容器可以用宿主的 GPU |
| Volume mount | ”Shared folder”(共用資料夾) | 把宿主的一個資料夾映射到容器裡面。容器停了,資料還在 |
| Base image | ”Starting point”(起點) | Dockerfile 裡 FROM 指定的那個 image,決定了預先裝好什麼 |
Docker 隔離的是整個軟體環境。 不只是 Python 套件,連 OS、C 函式庫、CUDA 驅動、Python 版本全部打包在一起。現階段不一定每個專案都要用,但之後要把 AI 應用交給別人跑或部署到伺服器上,Docker 就是「保證環境一模一樣」的工具。
AI 工程師不需要精通每一種語法,但要能看懂、能改。 Python 是主力語言,Shell、Dockerfile、YAML、JSON 是出差時會碰到的語言。多數時候是改現成的模板,不是從零寫。
Mac 跑完整套 AI 工程課程是可行的,但不是每一步都在本機做。 小模型、小資料集在本機用 CPU 或 MPS 跑。大模型訓練上 Google Colab。Docker 也一樣,需要 NVIDIA GPU 的 image 之後在雲端機器上跑就行。