Python uygulamaları için Docker imaj boyutunu küçültmek
Selamlar, bu yazımda Python uygulamalarının Docker imaj boyutunu nasıl küçülteceğimize bakacağız. Konu basit görünüyor ama detaya inince hangi tekniğin gerçekten kazandırdığını, hangisinin sadece kâğıt üstünde iyi durduğunu ayırt etmek lazım. Lafı çok uzatmadan başlayalım.
Bir gün baktığınızda küçücük bir Flask API'nin imajı 1.1GB olmuş oluyor. İşin tuhafı bu boyutun büyük kısmı uygulamanızın çalışırken hiç dokunmadığı şeyler: C derleyicisi, header dosyaları, pip cache'leri, kullanılmayan sistem paketleri. Küçük imaj demek hızlı deploy, autoscaling sırasında hızlı pull ve saldırgana daha az yüzey demek. Yani sadece estetik değil.
Şişkin başlangıç noktası
Çoğumuzun ilk yazdığı Dockerfile aşağı yukarı şudur:
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
python:3.12 base image'ı tek başına yaklaşık 1GB. İçinde tam Debian, gcc, Python development header'ları, bir sürü utility var. Bunların büyük kısmına ihtiyacınız yok.
Adım 1: python:slim'e geçin
Slim varyantı derleyiciyi ve dev paketlerini atar. Tek satır değişiklikle ciddi kazanç:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
--no-cache-dir pip'in indirdiği paketleri cache'lemesini engelliyor; bu da imaj içinde gereksiz kopya bırakmıyor. Sırf bu adımla 1GB civarından 280MB'a inebilirsiniz.
Adım 2: Multi-stage build
Bazı paketler (cryptography, pandas, lxml, psycopg2) kurulurken C derleyicisi ister. Derleyiciyi production imajında tutmaya gerek yok. Builder stage'de derleyip, sadece kurulan paketleri production'a kopyalıyoruz:
FROM python:3.12-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc g++ libpq-dev libffi-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 && rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
RUN useradd --create-home appuser
USER appuser
CMD ["python", "app.py"]
Buradaki kritik nokta /opt/venv numarası. Builder stage'de virtualenv'e kuruyoruz, sonra tek COPY --from=builder ile bütün ortamı production'a taşıyoruz. Production tarafında pip, setuptools, wheel zaten yok.
Adım 3: Alpine - en küçük imaj ama dikkat
Alpine inanılmaz küçük base image üretir. Ama glibc yerine musl libc kullanıyor; numpy, pandas, scipy gibi C extension'lı paketlerle bazen sıkıntı çıkar. Tecrübeyle söyleyebilirim ki saf Python'a yakın projelerde Alpine harika, ama ağır bilim/veri kütüphaneleri varsa slim'de kalmak daha az baş ağrıtır.
FROM python:3.12-alpine AS builder
RUN apk add --no-cache gcc musl-dev libffi-dev postgresql-dev
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-alpine
RUN apk add --no-cache libpq libffi
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
USER nobody
CMD ["python", "app.py"]
Adım 4: .dockerignore unutulmasın
Build context'e gereksiz dosya gönderirseniz hem build yavaşlar hem de yanlışlıkla .env, .git gibi şeyler imaja sızabilir. Şu kadarcık bir dosya çok iş görür:
__pycache__
*.pyc
.git
.env
.env.*
.venv
.pytest_cache
.mypy_cache
.ruff_cache
.coverage
dist
build
*.egg-info
tests
docs
*.md
.vscode
.idea
docker-compose*.yml
Dockerfile*
Sık karşılaşılan hatalar
- Install ve temizliği ayrı RUN'larda yapmak: Docker layer'ları katmanlı çalışır, yani önceki layer'da yazılan dosyayı sonraki layer'da silseniz bile imaj boyutu küçülmez.
apt-get installilerm -rf /var/lib/apt/lists/*aynı RUN içinde olmalı. - Builder'da pip cache mount'u atlamak: BuildKit ile
--mount=type=cache,target=/root/.cache/pipkullanırsanız tekrar build'lerde paketler yeniden indirilmez; imaja da sızmaz. - Slim üstünde -dev paketlerini bırakmak: Production'da
libpq-devdeğillibpq5istiyorsunuz. -dev paketleri sadece compile zamanı için. COPY . .ile her şeyi atmak:.dockerignoreyoksa.gitklasörü bile imaja girer. Bence en sık atlanan adım bu.- root kullanıcı bırakmak: Boyutla ilgili değil ama production'a gidecek imajda
USER appuserveyaUSER nobodyolsun. Bir alışkanlık meselesi.
Tam production Dockerfile
Hepsini birleştiren, sahada kullanabileceğiniz hal:
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl && \
rm -rf /var/lib/apt/lists/* && \
useradd --create-home appuser
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
COPY . .
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app:create_app()", "--bind", "0.0.0.0:8000", "--workers", "4"]
Kapanış
Bu yazıda Python imajını küçültmek için slim base, multi-stage build, Alpine ve .dockerignore gibi tekniklere baktık. Şahsi kanaatim, çoğu proje için sweet spot slim + multi-stage; tek bu değişiklikle imaj boyutu yüzde sekseni rahat düşer. Alpine'ı sadece bağımlılıklarınızın musl'da temiz derlendiğinden eminseniz seçin, yoksa kazandığınız megabayttan çok zaman kaybedersiniz. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
