การ Dockerize Python App
Node Dockerfile ที่คุณรู้จักอยู่แล้ว
หัวข้อที่มีชื่อว่า “Node Dockerfile ที่คุณรู้จักอยู่แล้ว”ถ้าคุณเคย containerize Node/TypeScript app มาก่อน คุณรู้ pattern: copy package.json, รัน npm ci, copy source, build และรัน Python Dockerfile ตาม multi-stage logic เดียวกัน — แต่มี best practices เฉพาะ Python บางอย่างที่คุณต้องเข้าใจ
เทียบกันชัดๆ: Node vs Python Dockerfile
หัวข้อที่มีชื่อว่า “เทียบกันชัดๆ: Node vs Python Dockerfile”# ---- Node / TypeScript multi-stage Dockerfile ----FROM node:20-alpine AS depsWORKDIR /appCOPY package*.json ./RUN npm ci --only=production
FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build
FROM node:20-alpine AS runnerWORKDIR /appENV NODE_ENV=productionCOPY --from=deps /app/node_modules ./node_modulesCOPY --from=builder /app/dist ./distRUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserEXPOSE 3000CMD ["node", "dist/main.js"]# ---- Python multi-stage Dockerfile ----FROM python:3.14-slim AS builderWORKDIR /app
# ติดตั้ง build tools แล้วสร้าง venv ภายใน imageRUN python -m venv /app/.venvENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.14-slim AS runnerWORKDIR /app
# Copy pre-built venv จาก builderCOPY --from=builder /app/.venv /app/.venvENV PATH="/app/.venv/bin:$PATH"
# Copy application sourceCOPY src/ ./src/
# รันด้วย non-root userRUN addgroup --system appgroup && adduser --system --ingroup appgroup appuserUSER appuser
EXPOSE 8000CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]เวอร์ชันที่ใช้ uv
หัวข้อที่มีชื่อว่า “เวอร์ชันที่ใช้ uv”ถ้าโปรเจกต์ใช้ uv (ดู venv, pip, uv และ Poetry) ให้ copy ตัว binary ของ uv มาจาก official image แล้วให้มันสร้าง environment จาก lock file วิธีนี้เร็วที่สุดและทำให้ image ตอน runtime ไม่มีเครื่องมือ build ติดไปด้วย
# ---- Node / TypeScript multi-stage Dockerfile ----FROM node:20-alpine AS depsWORKDIR /appCOPY package*.json ./RUN npm ci --only=production
FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build
FROM node:20-alpine AS runnerWORKDIR /appENV NODE_ENV=productionCOPY --from=deps /app/node_modules ./node_modulesCOPY --from=builder /app/dist ./distRUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserEXPOSE 3000CMD ["node", "dist/main.js"]# ---- uv multi-stage Dockerfile ----FROM python:3.14-slim AS builderCOPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uvWORKDIR /app
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
# ติดตั้ง dependency ก่อน — cache ไว้จนกว่า lock file จะเปลี่ยนCOPY pyproject.toml uv.lock ./RUN uv sync --locked --no-install-project --no-dev
COPY src/ ./src/RUN uv sync --locked --no-dev
FROM python:3.14-slim AS runnerWORKDIR /appCOPY --from=builder /app/.venv /app/.venvCOPY --from=builder /app/src ./srcENV PATH="/app/.venv/bin:$PATH"
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuserUSER appuser
EXPOSE 8000CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]--locked ทำให้ build ล้มเหลวถ้า uv.lock ไม่ตรงกับ pyproject.toml แทนที่จะ resolve เวอร์ชันใหม่เงียบ ๆ เทียบได้กับ npm ci ที่ไม่ยอมให้หลุดจาก package-lock.json
Python Dockerfile แบบ production เต็มรูปแบบ พร้อม comment
หัวข้อที่มีชื่อว่า “Python Dockerfile แบบ production เต็มรูปแบบ พร้อม comment”# syntax=docker/dockerfile:1
# ---- Stage 1: Build / dependency installation ----FROM python:3.14-slim AS builder
WORKDIR /app
# PYTHONDONTWRITEBYTECODE: don't write .pyc files (saves space)# PYTHONUNBUFFERED: log output immediately (12-Factor: log to stdout)ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1
# Create the virtual environment INSIDE the image# This lets us copy the entire /app/.venv to the final stageRUN python -m venv /app/.venvENV PATH="/app/.venv/bin:$PATH"
# Copy dependency manifest first (Docker layer caching)COPY pyproject.toml uv.lock* requirements.txt* ./
# Install dependencies — no cache to keep image smallRUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt
# ---- Stage 2: Runtime image ----FROM python:3.14-slim AS runner
WORKDIR /app
# Copy the fully-populated venv from builderCOPY --from=builder /app/.venv /app/.venvENV PATH="/app/.venv/bin:$PATH" \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1
# Copy application source (after deps — better cache hit on rebuilds)COPY src/ ./src/
# Create and switch to a non-root user (security best practice)RUN addgroup --system appgroup && \ adduser --system --ingroup appgroup --no-create-home appuserUSER appuser
# Expose the port the app listens onEXPOSE 8000
# CMD uses exec form (no shell wrapper — signals go directly to process)CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]ทำไมต้องสร้าง venv ภายใน Docker image?
หัวข้อที่มีชื่อว่า “ทำไมต้องสร้าง venv ภายใน Docker image?”รันในเทอร์มินัลของคุณเพื่อ build และรัน:
docker build -t my-python-app .docker run -p 8000:8000 my-python-app
# Or with docker compose:# docker compose up --build.dockerignore
หัวข้อที่มีชื่อว่า “.dockerignore”เหมือน .gitignore สำหรับ Docker สร้างเสมอเพื่อหลีกเลี่ยงการ copy venv, cache และ secrets ลงใน image:
.venv/__pycache__/*.pyc*.pyo.pytest_cache/.mypy_cache/.ruff_cache/.env.env.**.log