Ana içeriğe geç

Multi-Stage Dockerfile Şablonları#

Kopyala-değiştir. Üçü de aynı disipline uyar: build ve runtime ayrı stage, runtime non-root, final imaj küçük. Placeholder'lar <UPPER_CASE> ile. Bu sayfa komşu dosyaların gömülü halidir; kaynak dosyalar aynı klasörde.

Dosyalar#

Dosya Runtime tabanı Öne çıkan
go.Dockerfile distroless/static Statik binary, ~10 MB final imaj
node.Dockerfile node:22-alpine + tini deps/build/runtime 3 stage, prod-only modules
python.Dockerfile python:3.12-slim uv ile hızlı install, venv taşıma

Ortak karar: niye multi-stage#

  • Build araçları runtime'a sızmaz. Derleyici, dev bağımlılık, kaynak kod final imajda yok — saldırı yüzeyi ve CVE sayısı düşer.
  • Non-root runtime. Üçü de UID 65532 ile çalışır; compromise container root değil.
  • Cache mount (--mount=type=cache): bağımlılık indirmesi katman cache'ine değil, build cache'ine gider — reproducible ve hızlı.

1️⃣ Go — distroless static#

# Multi-stage Go build → distroless static (final imaj ~10 MB)
#
# Build:
#   docker build -f go.Dockerfile -t <REGISTRY>/<IMAGE>:<TAG> .
#
# Notes:
#   - CGO_ENABLED=0  → fully static binary
#   - distroless/static  → /etc/passwd, ca-certificates, tzdata var; shell yok

# ----- Stage 1: Build -----
FROM golang:1.23-alpine AS build

WORKDIR /src

# Bağımlılıkları önce kopyala (cache hit için)
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download

# Kaynak kodu kopyala
COPY . .

# Build (statik, küçük)
ARG VERSION=dev
ARG COMMIT=unknown
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux \
    go build \
      -trimpath \
      -ldflags="-w -s -X main.Version=${VERSION} -X main.Commit=${COMMIT}" \
      -o /out/app \
      ./cmd/app

# ----- Stage 2: Runtime -----
FROM gcr.io/distroless/static-debian12:nonroot

# Etiketler (registry'de görünür)
LABEL org.opencontainers.image.source="https://github.com/<ORG>/<REPO>"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.description="<APP_NAME>"

WORKDIR /app

# Sadece binary'yi kopyala
COPY --from=build /out/app /app/app

# Non-root user (distroless'in built-in nonroot user'ı UID 65532)
USER 65532:65532

EXPOSE 8080

# Healthcheck (opsiyonel — K8s probe varsa gerek yok)
# HEALTHCHECK CMD ["/app/app", "health"]

ENTRYPOINT ["/app/app"]

2️⃣ Node.js — 3 stage (deps / build / runtime)#

# Multi-stage Node.js build (TypeScript)
# - Build stage'de tüm dev dependencies var
# - Runtime'da sadece production deps + dist/

# ----- Stage 1: Dependencies -----
FROM node:22-alpine AS deps

WORKDIR /app

# package files'ı önce (cache hit)
COPY package.json package-lock.json* ./

# Production-only deps (runtime için)
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev --audit=false --fund=false

# ----- Stage 2: Build -----
FROM node:22-alpine AS build

WORKDIR /app

COPY package.json package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --audit=false --fund=false

COPY . .

# Build (TypeScript → JS, vite/webpack/tsc)
RUN npm run build

# ----- Stage 3: Runtime -----
FROM node:22-alpine AS runtime

# Non-root user (alpine'da 'node' user var)
RUN apk add --no-cache tini && \
    addgroup -g 65532 nonroot 2>/dev/null || true && \
    adduser -u 65532 -G nonroot -D nonroot 2>/dev/null || true

WORKDIR /app

# Sadece production node_modules + build çıktısı
COPY --from=deps  --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=build --chown=nonroot:nonroot /app/dist         ./dist
COPY --chown=nonroot:nonroot package.json ./

USER nonroot

EXPOSE 3000

ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=384"

# tini = PID 1, signal handling + zombie reap
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/index.js"]

# ───────────────────────────────────────────────────────────
# Alternatif: Chainguard ile (daha küçük, daha az CVE)
#
# FROM cgr.dev/chainguard/node:latest AS runtime
# WORKDIR /app
# COPY --from=deps  --chown=nonroot:nonroot /app/node_modules ./node_modules
# COPY --from=build --chown=nonroot:nonroot /app/dist         ./dist
# CMD ["dist/index.js"]

3️⃣ Python — uv ile hızlı install#

# Multi-stage Python build
# - uv ile hızlı dependency install (pip'ten 10-100x hızlı)
# - Distroless'a yakın final imaj
#
# Alternatif paketleyici: uv (Astral) | poetry | pip

# ----- Stage 1: Build venv -----
FROM python:3.12-slim AS build

# uv kurulum (Astral'in modern Python paketleyicisi)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy \
    UV_PYTHON_DOWNLOADS=0

WORKDIR /app

# Bağımlılıkları yükle (cache friendly)
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project --no-dev

# Uygulama kodunu kopyala
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev

# ----- Stage 2: Runtime -----
FROM python:3.12-slim AS runtime

# Güvenlik: non-root user
RUN groupadd -r app -g 65532 && \
    useradd -r -g app -u 65532 -m -d /home/app app && \
    apt-get update && \
    apt-get install -y --no-install-recommends ca-certificates && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# venv ve uygulama kodu
COPY --from=build --chown=app:app /app /app

# venv'i path'e ekle
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONHASHSEED=random

USER app

EXPOSE 8000

# uvicorn / gunicorn / fastapi
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

# ───────────────────────────────────────────────────────────
# Alternatif: Chainguard
#
# FROM cgr.dev/chainguard/python:latest AS runtime
# WORKDIR /app
# COPY --from=build --chown=nonroot:nonroot /app /app
# ENV PATH="/app/.venv/bin:$PATH"
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

🚫 Anti-Pattern#

Anti-pattern Niye kötü Doğru
Tek stage, build araçları imajda Derleyici + kaynak kod runtime'da → büyük saldırı yüzeyi Multi-stage; runtime'a sadece artefakt kopyala
latest base image Reproducible değil; sürpriz CVE Sürüm pinle (node:22-alpine), ideal SHA digest
Root user runtime Compromise container = root USER 65532 + non-root taban
COPY . . sonra .dockerignore yok node_modules, .git, secret sızar .dockerignore ekle, sadece gerekeni kopyala

"İmaj küçüldükçe hem push/pull hızlanır hem taranacak CVE azalır — küçük imaj güvenlik kararıdır."