Docker Multi-stage Build Optimizasyonu
Docker multi-stage build'ler, container image boyutunu dramatik olarak azaltmanın ve güvenliği artırmanın en etkili yöntemlerinden biridir. Bu rehberde, production-ready container image'ları oluşturmak için advanced Docker tekniklerini öğreneceksiniz.
İçindekiler
- Multi-stage Build Temelleri
- Layer Caching Optimizasyonu
- Security Best Practices
- Build Performance Tuning
- Advanced Patterns ve Teknikler
- CI/CD Pipeline Entegrasyonu
- Troubleshooting ve Debugging
Multi-stage Build Temelleri {#multi-stage-temelleri}
Single-stage vs Multi-stage Build Karşılaştırması
Problemli Single-stage Build:
# ❌ Kötü örnek - Single-stage build
FROM node:18
WORKDIR /app
# Tüm development dependencies dahil
COPY package*.json ./
RUN npm install
# Source code kopyala
COPY . .
# Build
RUN npm run build
# Production dependencies (gereksiz)
RUN npm install --only=production
EXPOSE 3000
CMD ["npm", "start"]
# Sonuç: ~800MB image boyutu
# İçerik: Build tools, development dependencies, source code, compiled code
Optimize Edilmiş Multi-stage Build:
# ✅ İyi örnek - Multi-stage build
# Stage 1: Build environment
FROM node:18-alpine AS builder
WORKDIR /app
# Package files'ları önce kopyala (cache optimization)
COPY package*.json ./
# Install all dependencies (dev + prod)
RUN npm ci --only=production && npm cache clean --force
# Source code'u kopyala
COPY . .
# Build the application
RUN npm run build
# Stage 2: Production environment
FROM node:18-alpine AS production
# Security: Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /app
# Copy production dependencies
COPY --from=builder /app/node_modules ./node_modules
# Copy built application
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Set ownership
RUN chown -R nextjs:nodejs /app
# Switch to non-root user
USER nextjs
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
CMD ["npm", "start"]
# Sonuç: ~150MB image boyutu (80% azalma)
# İçerik: Sadece runtime dependencies ve compiled code
Real-world Örnek: Go Application
# Go Multi-stage Build Example
# Stage 1: Build environment
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata
# Create non-root user for final stage
RUN adduser -D -s /bin/sh -u 1001 appuser
WORKDIR /src
# Copy go mod files first (for caching)
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download && go mod verify
# Copy source code
COPY . .
# Build the binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -extldflags '-static'" \
-a -installsuffix cgo \
-o app ./cmd/server
# Stage 2: Scratch-based minimal image
FROM scratch AS production
# Copy timezone data
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Copy CA certificates
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy user information
COPY --from=builder /etc/passwd /etc/passwd
# Copy the binary
COPY --from=builder /src/app /app
# Use non-root user
USER appuser
EXPOSE 8080
# Health check endpoint
HEALTHCHECK --interval=30s --timeout=3s CMD ["/app", "healthcheck"]
ENTRYPOINT ["/app"]
# Sonuç: ~10MB image boyutu (from scratch)
# İçerik: Sadece statically compiled binary
Layer Caching Optimizasyonu {#layer-caching}
Optimal Layer Ordering
# ✅ Optimized layer ordering
FROM node:18-alpine AS base
# 1. Install system dependencies (rarely changes)
RUN apk add --no-cache \
libc6-compat \
openssl \
&& rm -rf /var/cache/apk/*
WORKDIR /app
# 2. Copy package manager files (changes less frequently)
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
# 3. Install dependencies
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
else echo "Lockfile not found." && exit 1; \
fi
# 4. Copy source code (changes most frequently)
COPY . .
# Build stage
FROM base AS builder
RUN npm run build
# Production stage
FROM base AS production
COPY --from=builder /app/.next ./.next
CMD ["npm", "start"]
Build Context Optimization
# .dockerignore - Exclude unnecessary files
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
# Development files
src/**/*.test.js
src/**/*.spec.js
**/__tests__
**/.coverage
**/coverage
# IDE files
.vscode
.idea
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs
*.log
lerna-debug.log
# Build artifacts (if copying from host)
dist
build
.next
Layer Caching Strategies
#!/bin/bash
# Build with cache optimization
# 1. Use BuildKit for advanced caching
export DOCKER_BUILDKIT=1
# 2. Build with cache mount for package managers
docker build \
--target production \
--cache-from myregistry.com/myapp:cache \
--tag myregistry.com/myapp:latest \
--tag myregistry.com/myapp:cache \
.
# 3. Push cache layer
docker push myregistry.com/myapp:cache
# 4. Multi-platform build with cache
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref=myregistry.com/myapp:cache \
--cache-to type=registry,ref=myregistry.com/myapp:cache,mode=max \
--tag myregistry.com/myapp:latest \
--push .
Security Best Practices {#security-practices}
Distroless Images
# Python application with distroless
FROM python:3.11-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Build/compile if needed
RUN python -m compileall .
# Final stage with distroless
FROM gcr.io/distroless/python3-debian11
# Copy Python packages
COPY --from=builder /root/.local /root/.local
# Copy application
COPY --from=builder /app /app
WORKDIR /app
# Update PATH
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["app.py"]
# Security benefits:
# - No shell access
# - No package manager
# - Minimal attack surface
# - Non-root user by default
Security Scanning Integration
# Dockerfile with security annotations
FROM node:18-alpine AS base
# Vulnerability: Use specific version tags
# hadolint ignore=DL3018
RUN apk add --no-cache \
# Pin package versions for security
curl=8.0.1-r0 \
ca-certificates=20230506-r0
# Security: Create user with specific UID/GID
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001 -G nodejs
WORKDIR /app
# Security: Copy with specific ownership
COPY --chown=nextjs:nodejs package*.json ./
USER nextjs
RUN npm ci --only=production && npm cache clean --force
COPY --chown=nextjs:nodejs . .
# Security: Don't run as root
USER nextjs
EXPOSE 3000
# Security: Use exec form for proper signal handling
CMD ["node", "server.js"]
#!/bin/bash
# Security scanning pipeline
set -e
IMAGE_NAME="myapp:latest"
echo "🔍 Building image..."
docker build -t "$IMAGE_NAME" .
echo "🔍 Scanning with Trivy..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --exit-code 1 --severity HIGH,CRITICAL "$IMAGE_NAME"
echo "🔍 Scanning with Snyk..."
snyk container test "$IMAGE_NAME" --severity-threshold=high
echo "🔍 Scanning with Grype..."
grype "$IMAGE_NAME" --fail-on high
echo "✅ Security scans passed!"
Build Performance Tuning {#performance-tuning}
Parallel Builds
# Parallel build example
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
# Stage for dependencies
FROM base AS deps
RUN npm ci --only=production && npm cache clean --force
# Stage for dev dependencies and build
FROM base AS build-deps
RUN npm ci && npm cache clean --force
# Build stage
FROM build-deps AS builder
COPY . .
RUN npm run build
# Test stage (runs in parallel with builder)
FROM build-deps AS tester
COPY . .
RUN npm run test
RUN npm run lint
# Final stage
FROM base AS production
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
# Copy from multiple stages
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
CMD ["npm", "start"]
BuildKit Advanced Features
# syntax=docker/dockerfile:1
FROM node:18-alpine AS base
# Mount caches for package managers
RUN --mount=type=cache,target=/root/.npm \
--mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
npm ci --only=production
# Secret mount for private repos
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --only=production
# Bind mount for source (zero copy)
RUN --mount=type=bind,source=.,target=/src,rw \
--mount=type=cache,target=/app/.next/cache \
cd /src && npm run build && cp -r .next /app/
COPY --from=base /app/.next ./.next
CMD ["npm", "start"]
Advanced Patterns ve Teknikler {#advanced-patterns}
Multi-Architecture Support
# syntax=docker/dockerfile:1
# Multi-arch base selection
FROM --platform=$BUILDPLATFORM node:18-alpine AS base
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"
# Architecture-specific optimizations
RUN case "$TARGETPLATFORM" in \
"linux/amd64") \
echo "Optimizing for amd64" && \
apk add --no-cache libc6-compat ;; \
"linux/arm64") \
echo "Optimizing for arm64" && \
apk add --no-cache libc6-compat ;; \
*) \
echo "Unknown platform $TARGETPLATFORM" ;; \
esac
WORKDIR /app
# Build stage
FROM base AS builder
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM base AS production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/package.json ./
RUN npm ci --only=production
CMD ["npm", "start"]
Dynamic Stage Selection
# syntax=docker/dockerfile:1
ARG BUILD_ENV=production
# Development stage
FROM node:18-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# Production build stage
FROM node:18-alpine AS build-production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production runtime stage
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=build-production /app/.next ./.next
COPY --from=build-production /app/node_modules ./node_modules
CMD ["npm", "start"]
# Final stage selection based on BUILD_ENV
FROM ${BUILD_ENV} AS final
Shared Layer Optimization
# Base layer for multiple services
FROM node:18-alpine AS node-base
# Common system dependencies
RUN apk add --no-cache \
curl \
ca-certificates \
tzdata
# Common user setup
RUN addgroup -g 1001 -S nodejs && \
adduser -S appuser -u 1001 -G nodejs
WORKDIR /app
# Service A
FROM node-base AS service-a-builder
COPY service-a/package*.json ./
RUN npm ci
COPY service-a/ .
RUN npm run build
FROM node-base AS service-a
COPY --from=service-a-builder /app/dist ./dist
COPY --from=service-a-builder /app/node_modules ./node_modules
USER appuser
CMD ["node", "dist/index.js"]
# Service B
FROM node-base AS service-b-builder
COPY service-b/package*.json ./
RUN npm ci
COPY service-b/ .
RUN npm run build
FROM node-base AS service-b
COPY --from=service-b-builder /app/dist ./dist
COPY --from=service-b-builder /app/node_modules ./node_modules
USER appuser
CMD ["node", "dist/server.js"]
CI/CD Pipeline Entegrasyonu {#cicd-integration}
GitHub Actions ile Docker Build
# .github/workflows/docker-build.yml
name: Docker Build and Push
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_ENV=production
GIT_SHA=${{ github.sha }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
Docker Compose ile Development Environment
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development
args:
BUILD_ENV: development
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
- /app/.next
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
depends_on:
- postgres
- redis
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
# Production override
# docker-compose.prod.yml
version: '3.8'
services:
app:
build:
target: production
args:
BUILD_ENV: production
ports:
- "80:3000"
environment:
- NODE_ENV=production
volumes: []
restart: unless-stopped
Troubleshooting ve Debugging {#troubleshooting}
Debug Multi-stage Builds
#!/bin/bash
# Debug script for multi-stage builds
IMAGE_NAME="myapp"
echo "🔍 Inspecting build stages..."
# Build and tag each stage
docker build --target builder --tag "${IMAGE_NAME}:builder" .
docker build --target production --tag "${IMAGE_NAME}:production" .
# Inspect each stage
echo "📊 Builder stage size:"
docker images "${IMAGE_NAME}:builder" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
echo "📊 Production stage size:"
docker images "${IMAGE_NAME}:production" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
# Analyze layers
echo "🔍 Analyzing layers..."
docker history "${IMAGE_NAME}:production"
# Run interactive debug session
echo "🐛 Starting debug session..."
docker run -it --rm "${IMAGE_NAME}:builder" /bin/sh
Common Issues ve Solutions
# Issue: Large node_modules in final image
# ❌ Problem
FROM node:18-alpine
COPY . .
RUN npm install
CMD ["npm", "start"]
# ✅ Solution: Multi-stage build
FROM node:18-alpine AS builder
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]
# Issue: Cache invalidation on every build
# ❌ Problem
FROM node:18-alpine
COPY . .
RUN npm install
# ✅ Solution: Optimize copy order
FROM node:18-alpine
COPY package*.json ./
RUN npm ci
COPY . .
# Issue: Security vulnerabilities
# ❌ Problem
FROM node:18
# ✅ Solution: Use minimal base + security scanning
FROM node:18-alpine
RUN apk add --no-cache dumb-init
USER node
ENTRYPOINT ["dumb-init", "--"]
CMD ["npm", "start"]
Performance Profiling
#!/bin/bash
# Docker build performance profiling
# Enable BuildKit experimental features
export DOCKER_BUILDKIT=1
export BUILDKIT_PROGRESS=plain
# Build with detailed timing
time docker build \
--progress=plain \
--no-cache \
-t myapp:test .
# Analyze build context size
echo "📊 Build context analysis:"
tar -czf - . | wc -c | numfmt --to=iec
# Check .dockerignore effectiveness
echo "📊 Files being sent to Docker daemon:"
tar -czf - . --exclude-from=.dockerignore | wc -c | numfmt --to=iec
# Layer size analysis
echo "📊 Layer size analysis:"
docker history myapp:test --format "{{.CreatedBy}}: {{.Size}}"
Sonuç
Docker multi-stage build optimizasyonu, modern containerization stratejisinin temelini oluşturur. Bu rehberde ele aldığımız teknikler:
- Image Size Reduction: %80'e varan boyut azaltması
- Security Enhancement: Minimal attack surface ve distroless images
- Build Performance: Layer caching ve parallel builds
- Production Readiness: Health checks, non-root users, proper signal handling
Bu stratejileri uygulayarak, güvenli, hızlı ve efficient container image'ları oluşturabilirsiniz.
TekTık Yazılım olarak, Docker containerization ve DevOps optimizasyon stratejileri konularında profesyonel danışmanlık hizmeti vermekteyiz. İletişim sayfamızdan bizimle iletişime geçebilirsiniz.