Dockerfile 入門
Dockerfile 是一份純文字檔,一行一行寫下「怎麼做出一個 Image」:從哪個基礎環境開始、要複製哪些檔案、要安裝什麼、最後執行什麼指令。寫好之後,用 docker build 就能把自己的程式打包成 Image,再用 docker run 執行。本頁先帶你做出第一個 Image,再說明快取、多階段建置與常用指令。
動手做:打包一個 Python 網站
1. 準備程式
建立一個資料夾,放入以下三個檔案。
app.py:一個最簡單的 Flask 網站。
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
return "Hello Docker!\n"
@app.get("/health")
def health():
return "OK\n"
requirements.txt:程式需要的套件。
.dockerignore:列出不要放進 Image 的檔案,就像 .gitignore。
2. 寫 Dockerfile
在同一個資料夾建立名為 Dockerfile 的檔案(沒有副檔名):
# 1. 基底映像檔:已經裝好 Python 3.12 的精簡版 Linux
FROM python:3.12-slim
# 2. 之後的指令都在 /app 資料夾裡執行
WORKDIR /app
# 3. 先複製套件清單並安裝(這一層可以被快取)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 再複製程式碼(最常改的放最後)
COPY . .
# 5. 改用一般使用者執行,比 root 安全
RUN useradd --create-home appuser
USER appuser
# 6. 說明這個容器會用 8000 埠(只是註記,不會真的開放)
EXPOSE 8000
# 7. 容器啟動時要執行的指令
CMD ["flask", "--app", "app", "run", "--host", "0.0.0.0", "--port", "8000"]
3. 建置並執行
-t hello-docker:1.0:幫 Image 取名字和版本。- 最後的
.:代表「用目前這個資料夾的檔案來建置」。
打開 http://localhost:8000,看到 Hello Docker! 就成功了。用 docker exec hello whoami 可以確認程式是以 appuser 身分執行,而不是 root。看完用 docker stop hello 停止。
為什麼要 --host 0.0.0.0?
程式預設只接受「自己」的連線(127.0.0.1)。在容器裡,「自己」指的是容器本身,所以從你的電腦連不進去。改成 0.0.0.0 才會接受外部的連線。
為什麼要先複製 requirements.txt?
Dockerfile 的每個 RUN、COPY 都會產生一層(layer)。建置時,Docker 會檢查每一層「跟上次比有沒有變」,沒變就直接沿用上次的結果,這叫做快取(cache)。
但只要某一層變了,它後面的所有層都要重做。所以順序很重要:
- 套件清單很少改,放前面 → 安裝套件那一層幾乎每次都能用快取。
- 程式碼常常改,放後面 → 改程式時只需要重做最後幾層。
試著修改 app.py 再建置一次,會看到安裝套件的步驟顯示 CACHED,幾秒鐘就建置完成。如果把 COPY . . 放在 pip install 之前,每次改程式都要重新安裝所有套件。
多階段建置:讓 Image 變小
有些程式需要先「建置」才能執行,例如前端網站要先用 Node.js 打包。但打包工具在執行時用不到,放在 Image 裡只會讓它變大。
多階段建置(multi-stage build)可以在一個 Dockerfile 裡用兩個 FROM:第一階段負責建置,第二階段只拿建置好的結果。
以下範例假設專案裡有 public/ 網頁資料夾,以及一個 package.json,執行 npm run build 會把網頁輸出到 dist/ 資料夾:
# 第一階段:用 Node.js 建置網站
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json ./
RUN npm install
COPY public ./public
RUN npm run build
# 第二階段:只把建置結果交給 Nginx
FROM nginx:1.28-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
AS build 幫第一階段取名字,COPY --from=build 從第一階段複製檔案。最後的 Image 只有 Nginx 和網頁檔案,完全不含 Node.js。
常用指令一覽
| 指令 | 用途 | 範例 |
|---|---|---|
FROM |
選擇基底映像檔,每個 Dockerfile 都從它開始 | FROM python:3.12-slim |
WORKDIR |
設定工作資料夾,不存在會自動建立 | WORKDIR /app |
COPY |
把檔案從電腦複製到 Image | COPY . . |
RUN |
建置時執行指令,例如安裝套件 | RUN pip install -r requirements.txt |
ENV |
設定環境變數,容器執行時也看得到 | ENV TZ=Asia/Taipei |
ARG |
建置時才用的變數,用 --build-arg 傳入 |
ARG VERSION=1.0 |
USER |
切換執行身分 | USER appuser |
EXPOSE |
註記容器使用的連接埠(只是說明) | EXPOSE 8000 |
CMD |
容器啟動時的預設指令 | CMD ["python", "app.py"] |
ENTRYPOINT |
容器固定要執行的程式 | ENTRYPOINT ["gunicorn"] |
HEALTHCHECK |
定期檢查程式是否正常 | 見下方範例 |
ADD |
類似 COPY,但會自動解壓縮 tar 檔 |
一般情況用 COPY 就好 |
LABEL |
加上說明資訊,例如作者、版本 | LABEL version="1.0" |
VOLUME |
標示這個資料夾存放資料 | VOLUME /data |
只有 RUN、COPY、ADD 會真的在 Image 裡增加檔案;其他指令只是修改設定。完整說明請看 Dockerfile Reference。
CMD 和 ENTRYPOINT 差在哪?
兩者都是設定「容器啟動時要執行什麼」,差別在能不能被取代:
- CMD:預設指令。執行
docker run 映像檔 其他指令時,後面的指令會取代 CMD。 - ENTRYPOINT:固定要執行的程式。
docker run後面接的東西會變成它的參數。
這個 Image 預設執行 ping localhost;執行 docker run 映像檔 google.com 則變成 ping google.com。
大部分情況只用 CMD 就夠了。記得使用 ["程式", "參數"] 這種方括號寫法,程式才能正確收到停止訊號,docker stop 時會比較快結束。
健康檢查
HEALTHCHECK 讓 Docker 定期確認程式是否正常,docker ps 會顯示 healthy 或 unhealthy。檢查用的指令必須在 Image 裡存在:python:3.12-slim 沒有 curl,可以改用 Python 來檢查:
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
Image 名稱怎麼看?
完整的 Image 名稱格式是:
| Image 名稱 | 意思 |
|---|---|
python:3.12-slim |
Docker Hub 官方的 python,標籤 3.12-slim |
myuser/myapp:1.0 |
Docker Hub 上 myuser 帳號的 myapp,版本 1.0 |
ghcr.io/acme/api:prod |
GitHub Container Registry 上 acme 組織的 api |
沒寫倉庫位址就是 Docker Hub;沒寫標籤就是 latest。
寫 Dockerfile 的好習慣
- 一定要有
.dockerignore,避免把.git、.env(密碼)放進 Image。 - 基底映像檔寫清楚版本,例如
python:3.12-slim,不要只寫python。 - 不常變的步驟放前面,程式碼放最後,善用快取。
- 用
USER改成一般使用者執行。 - 不要把密碼、金鑰寫在 Dockerfile 裡,任何拿到 Image 的人都看得到。
- 需要建置步驟的程式,使用多階段建置。
參考資料
推薦影音
Writing a Dockerfile
簡述:Docker 官方短片,介紹 Dockerfile 的基本結構,對應本頁「動手做」。(Docker 官方)
Using the build cache
簡述:Docker 官方短片,說明快取什麼時候有效、什麼時候失效,對應本頁「為什麼要先複製 requirements.txt?」。(Docker 官方)
Multi-stage builds
簡述:Docker 官方短片,示範用多個 FROM 讓最後的 Image 更小,對應本頁「多階段建置」。(Docker 官方)
更多影片請見 影音教學。