ข้ามไปยังเนื้อหา

การ Dockerize Python App

ถ้าคุณเคย containerize Node/TypeScript app มาก่อน คุณรู้ pattern: copy package.json, รัน npm ci, copy source, build และรัน Python Dockerfile ตาม multi-stage logic เดียวกัน — แต่มี best practices เฉพาะ Python บางอย่างที่คุณต้องเข้าใจ

TypeScript
# ---- Node / TypeScript multi-stage Dockerfile ----
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]
Python
# ---- Python multi-stage Dockerfile ----
FROM python:3.14-slim AS builder
WORKDIR /app
# ติดตั้ง build tools แล้วสร้าง venv ภายใน image
RUN python -m venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.14-slim AS runner
WORKDIR /app
# Copy pre-built venv จาก builder
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
# Copy application source
COPY src/ ./src/
# รันด้วย non-root user
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

ถ้าโปรเจกต์ใช้ uv (ดู venv, pip, uv และ Poetry) ให้ copy ตัว binary ของ uv มาจาก official image แล้วให้มันสร้าง environment จาก lock file วิธีนี้เร็วที่สุดและทำให้ image ตอน runtime ไม่มีเครื่องมือ build ติดไปด้วย

TypeScript
# ---- Node / TypeScript multi-stage Dockerfile ----
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]
Python
# ---- uv multi-stage Dockerfile ----
FROM python:3.14-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /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 runner
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src ./src
ENV PATH="/app/.venv/bin:$PATH"
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

--locked ทำให้ build ล้มเหลวถ้า uv.lock ไม่ตรงกับ pyproject.toml แทนที่จะ resolve เวอร์ชันใหม่เงียบ ๆ เทียบได้กับ npm ci ที่ไม่ยอมให้หลุดจาก package-lock.json

# 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 stage
RUN python -m venv /app/.venv
ENV 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 small
RUN 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 builder
COPY --from=builder /app/.venv /app/.venv
ENV 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 appuser
USER appuser
# Expose the port the app listens on
EXPOSE 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"]

รันในเทอร์มินัลของคุณเพื่อ build และรัน:

Terminal window
docker build -t my-python-app .
docker run -p 8000:8000 my-python-app
# Or with docker compose:
# docker compose up --build

เหมือน .gitignore สำหรับ Docker สร้างเสมอเพื่อหลีกเลี่ยงการ copy venv, cache และ secrets ลงใน image:

.venv/
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.mypy_cache/
.ruff_cache/
.env
.env.*
*.log
ทำไมจึงควรสร้าง virtual environment ภายใน Docker image แทนที่จะใช้ system Python?
PYTHONUNBUFFERED=1 รับประกันอะไรใน containerized Python app?
ใน multi-stage Dockerfile คุณนำ installed packages จาก builder stage เข้าสู่ runner stage อย่างไร?