快轉到主要內容
  1. 教學文章/

Docker for Python:讓你的程式在任何地方都能跑

·6 分鐘· loading · loading · ·
Python Docker Container Devops 部署
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 23: 本文

一. 前言
#

你有沒有遇過這種悲劇:在自己的電腦上跑得好好的 Python 程式,部署到伺服器或同事的電腦上就各種炸裂?🤯

ModuleNotFoundError: No module named 'pandas'
ERROR: Python 3.12 is required but found 3.9

「在我的電腦上可以跑啊!」—— 這句話大概是所有工程師最常說的名言了。

Docker 就是為了解決這個問題而生的!它把你的程式碼、Python 版本、所有相依套件、甚至作業系統環境,全部打包成一個容器(Container)。不管在哪台機器上執行,結果都一模一樣。

今天拍拍君就來帶大家從零開始學 Docker,讓你的 Python 專案從此告別「在我電腦上可以跑」的噩夢!🐳

二. 安裝 Docker
#

macOS
#

最簡單的方式是安裝 Docker Desktop:

# 用 Homebrew 安裝
brew install --cask docker

安裝完成後,打開 Docker Desktop 應用程式,等到狀態列的小鯨魚圖示不再跳動就代表 Docker daemon 已經啟動。

Linux (Ubuntu/Debian)
#

# 官方安裝腳本(最快的方式)
curl -fsSL https://get.docker.com | sh

# 讓你的使用者不用每次都 sudo
sudo usermod -aG docker $USER

安裝完成後記得登出再登入,讓群組權限生效。

驗證安裝
#

docker --version
# Docker version 27.5.1, build 9f9e405

docker run hello-world
# Hello from Docker! 🎉

如果看到 Hello from Docker! 就代表安裝成功了!

三. Docker 基本概念
#

在開始寫 Dockerfile 之前,先來搞清楚幾個核心概念:

概念 說明 比喻
Image(映像檔) 應用程式的快照,包含所有需要的檔案 🍰 蛋糕食譜
Container(容器) Image 的執行實例 🎂 用食譜做出來的蛋糕
Dockerfile 建構 Image 的指令檔 📝 寫食譜的步驟
Registry 存放 Image 的倉庫(如 Docker Hub) 📚 食譜圖書館

一個 Image 可以跑出多個 Container,就像一份食譜可以做出很多個蛋糕一樣。

Dockerfile  →  docker build  →  Image  →  docker run  →  Container
(食譜)        (烤蛋糕)       (成品)     (開吃)       (正在被吃的蛋糕)

四. 你的第一個 Dockerfile
#

假設我們有一個超級簡單的 Python 應用:

# app.py
from datetime import datetime

def main():
    print(f"🐍 拍拍君的 Docker 應用啟動了!")
    print(f"⏰ 現在時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"✅ 環境一切正常,容器運作中!")

if __name__ == "__main__":
    main()

現在來寫 Dockerfile:

# 使用 Python 3.12 的輕量級映像檔
FROM python:3.12-slim

# 設定工作目錄
WORKDIR /app

# 複製程式碼
COPY app.py .

# 執行指令
CMD ["python", "app.py"]

建構並執行:

# 建構 Image(-t 是給它一個名字)
docker build -t pypy-app .

# 執行 Container
docker run pypy-app
# 🐍 拍拍君的 Docker 應用啟動了!
# ⏰ 現在時間:2026-02-25 03:00:00
# ✅ 環境一切正常,容器運作中!

恭喜!你的第一個 Docker 容器跑起來了!🎉

五. 處理 Python 相依套件
#

真實的專案一定有套件相依,讓我們來看看怎麼正確處理:

# requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.34.0
httpx==0.28.0
# main.py
from fastapi import FastAPI

app = FastAPI(title="拍拍君的 API")

@app.get("/")
async def root():
    return {"message": "哈囉!我是跑在 Docker 裡的拍拍君 🐳"}

@app.get("/health")
async def health():
    return {"status": "ok", "container": True}

對應的 Dockerfile:

FROM python:3.12-slim

WORKDIR /app

# 先複製 requirements.txt,利用 Docker 的 layer cache
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 再複製程式碼(這樣改程式碼不會重新安裝套件)
COPY . .

# 開放 port
EXPOSE 8000

# 啟動 FastAPI
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

🔑 Layer Cache 的重要性
#

Docker 的每一行指令都會產生一個 layer。如果某個 layer 沒有變動,Docker 會直接使用快取,不需要重新執行。

這就是為什麼我們要先 COPY requirements.txt,再 COPY 程式碼

# ✅ 好的做法:分開 COPY
COPY requirements.txt .          # Layer 1:很少變動
RUN pip install -r requirements.txt  # Layer 2:很少重建
COPY . .                         # Layer 3:常常變動

# ❌ 不好的做法:一起 COPY
COPY . .                         # 程式碼一改,下面全部重來
RUN pip install -r requirements.txt  # 每次都要重新安裝 😭

建構並執行:

docker build -t pypy-api .
docker run -p 8000:8000 pypy-api

打開瀏覽器連 http://localhost:8000,就能看到拍拍君的 API 回應了!

六. Multi-stage Build:讓 Image 更小
#

預設的 Python image 通常很大(接近 1 GB)。用 multi-stage build 可以大幅縮小最終的 image 大小:

# ===== Stage 1: 建構階段 =====
FROM python:3.12-slim AS builder

WORKDIR /app

# 安裝建構相依(如果需要編譯 C extension)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ===== Stage 2: 執行階段 =====
FROM python:3.12-slim

WORKDIR /app

# 只複製安裝好的套件,不帶編譯工具
COPY --from=builder /install /usr/local

COPY . .

# 用非 root 使用者執行(安全最佳實踐)
RUN useradd --create-home appuser
USER appuser

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

比較一下大小差異:

docker images
# REPOSITORY    TAG       SIZE
# pypy-api      v1        1.02GB   ← 沒有 multi-stage
# pypy-api      v2        187MB    ← 有 multi-stage 🎉

差了 5 倍以上!而且更安全,因為最終 image 裡面沒有 gcc 等編譯工具。

💡 選擇正確的 Base Image
#

Image 大小 適用場景
python:3.12 ~1 GB 需要完整系統工具
python:3.12-slim ~150 MB 大多數情況的最佳選擇 ✅
python:3.12-alpine ~50 MB 最小,但可能有 musl 相容問題

拍拍君的建議:預設用 slim,除非你有特殊需求再考慮 alpine

七. Docker Compose:多容器協作
#

真實的應用通常不只一個容器。例如一個 API + 一個 PostgreSQL 資料庫 + 一個 Redis 快取。Docker Compose 讓你用一個 YAML 檔案管理多個容器:

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://pypy:secret@db:5432/pypydb
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: pypy
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: pypydb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pypy"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

啟動整個應用:

# 啟動所有容器(背景執行)
docker compose up -d

# 查看狀態
docker compose ps

# 看 log
docker compose logs -f api

# 停止並清除
docker compose down

只需要一個指令,三個容器就全部跑起來了!而且 Docker Compose 會自動建立一個內部網路,讓容器之間可以用服務名稱互相連線(例如 dbcache)。

八. 實用技巧與最佳實踐
#

1. 善用 .dockerignore
#

就像 .gitignore 一樣,.dockerignore 可以避免不必要的檔案被複製到 image 中:

# .dockerignore
__pycache__/
*.pyc
.git/
.venv/
.env
*.md
tests/
.pytest_cache/
.mypy_cache/
.ruff_cache/

2. 用環境變數管理設定
#

# 在 Dockerfile 中設定預設值
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV APP_ENV=production
# 執行時覆蓋
docker run -e APP_ENV=development pypy-api

PYTHONUNBUFFERED=1 特別重要——它確保 Python 的 print 輸出會即時顯示在 docker logs 中,不會被緩衝區吃掉。

3. Health Check
#

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"

4. 開發時使用 Volume Mount
#

寫程式時不需要每次都重新 build,用 volume 把本機程式碼掛進去:

docker run -p 8000:8000 \
    -v $(pwd):/app \
    pypy-api \
    uvicorn main:app --host 0.0.0.0 --reload

--reload 會自動偵測檔案變動並重新載入,開發體驗跟本機幾乎一樣!

5. 常用指令速查
#

# 列出所有容器
docker ps -a

# 進入執行中的容器
docker exec -it <container_id> bash

# 清除未使用的資源
docker system prune -a

# 查看 image 的層級結構
docker history pypy-api

# 複製檔案到容器內/外
docker cp local_file.txt container_id:/app/
docker cp container_id:/app/output.csv ./

結語
#

恭喜你學會了 Docker 的核心技能!🎉 讓我們回顧一下今天學到的內容:

  • ✅ Docker 的基本概念:Image、Container、Dockerfile
  • ✅ 撰寫 Dockerfile 並善用 layer cache
  • ✅ Multi-stage build 大幅縮小 image 大小
  • ✅ Docker Compose 管理多容器應用
  • .dockerignore、環境變數、health check 等最佳實踐

Docker 已經是現代 Python 開發者的必備技能了。不管你是要部署 API、跑資料處理 pipeline、還是建立可重現的開發環境,Docker 都能幫你搞定。

下次再也不用說「在我的電腦上可以跑」了——因為有了 Docker,哪台電腦都可以跑!🐳

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 23: 本文

相關文章

Streamlit:用 Python 快速打造互動式資料應用
·8 分鐘· loading · loading
Python Streamlit Data-Visualization Web-App Dashboard
Python Logging:別再 print 了,用正經的方式記錄日誌吧
·6 分鐘· loading · loading
Python Logging Debug 標準庫
Pre-commit Hooks:讓壞 Code 連 Commit 的機會都沒有
·4 分鐘· loading · loading
Python Pre-Commit Git Linter Code-Quality Ruff Mypy
Polars:比 Pandas 快 10 倍的 DataFrame 新選擇
·6 分鐘· loading · loading
Python Polars Dataframe 資料分析 Rust
PyTorch 神經網路入門:從零開始建立你的第一個模型
·5 分鐘· loading · loading
Python Pytorch Neural-Network Deep-Learning Machine-Learning
少寫一半程式碼:dataclasses 讓你的 Python 類別煥然一新
·6 分鐘· loading · loading
Python Dataclasses Oop 標準庫