Multi-stage Build Olmadan Image Şişti
SDK katmanı production'a binince 1.4 GB oldu

İlk üretim imajı 1.4 GB geldi. Servis bir API'ydi, statik bir site değildi. docker history satırları yalan söylemedi: SDK, NuGet cache, node_modules, apt listeleri, kaynak ağacı. Çalışan süreç bunların çoğunu açmıyordu. Taşıyorduk.
Tek FROM ile başlamak doğal. Derle, yayınla, aynı katman. Lokal'de 'çalışıyor'. Registry faturası, pull süresi ve saldırı yüzeyi sonra görünür. Multi-stage bir incelik değil; derleme odası ile koşu alanını ayırmak.
Ne şişiriyor
Derleyici. Header paketleri. Test runner. apt-get update sonrası kalan lists. COPY . . ile gelen .git ve belgeler — onu sonraki yazıda ayrı dağıtacağım. npm'in devDependency'si production node_modules içinde kaldıysa, imaj bir laptop kopyasıdır.
- sdk / build-essential: derleme bits, runtime gerekmez.
- Paket yöneticisi cache:
npm cisonrası cache silinmezse katmanda kalır. - Kaynak:
src/runtime'da durmak zorunda değil. - Debug araçları: curl, vim, testdisk. İmajı 'rahat' eder, yüzeyi büyütür.
İki oda
Build aşaması SDK imajı. Orada restore, compile, test. Son aşama runtime imajı. Orada sadece çıktı, kullanıcı, port. COPY --from=build bir köprü. Köprüden geçen her dosya bilinçli. 'Hepsini kopyala' köprü değil, ikinci bir şişme.
FROM mcr.microsoft.com/dotnet/sdk:8.0.403-bookworm-slim AS build
WORKDIR /src
COPY Directory.Build.props ./
COPY src/Api/Api.csproj src/Api/
RUN dotnet restore src/Api/Api.csproj
COPY src/ src/
RUN dotnet publish src/Api/Api.csproj -c Release -o /out --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:8.0.10-bookworm-slim
WORKDIR /app
RUN useradd --system --uid 10001 app
USER app
COPY --from=build --chown=app:app /out ./
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Api.dll"]
Restore'u kopyalamadan önce yapmak layer cache'i de kurtarır; onu serinin cache yazısına bırakıyorum. Buradaki kazanç: son imajda csc yok, /root/.nuget yok, .cs yok. DLL ve bağımlılık. 1.4 GB → 180 MB bu ayrımla geldi. Sihir değil, kopyalamama.
apt'i son kata taşımak
Runtime'da native kütüphane gerekiyorsa onu son katta, ince imajda kur. Build katında kurup unutmak, kütüphaneyi son imaja taşımaz. Tersi de var: son katta build-essential açmak, multi-stage'i bozar. apt-get sonrası rm -rf /var/lib/apt/lists/* aynı RUN içinde olmalı. Ayrı satır, ayrı katman, lists durur.
Node tarafında npm ci --omit=dev build'de değil, runtime'a giden ağaçta. Build için devDependency lazımsa iki npm ci yap: biri derleme, biri üretim kopyası. Tek node_modules her ikisine hizmet etmez.
İnce imaj yetmezse
alpine cazip, glibc bekleyen native modülde kırılır. Distroless daha ince, kabuk götürür — onu ayrı yazdım. Önce multi-stage. Distro tartışması, derleyiciyi hâlâ imajda taşıyorsan kosmetik.
history satır satır
docker history --no-trunc --format "{{{{.Size}}}} {{{{.CreatedBy}}}}" yalan söylemez. İlk baktığımda üç satır şişkindi: dotnet restore, COPY . ., apt-get. Restore, SDK imajındaydı ve son kata sızmıştı çünkü tek stage'tim. COPY, .cs ve .git taşıyordu. apt, lists'i ayrı RUN ile bırakmıştı.
Aynı komutu multi-stage sonra tekrar çalıştırdım. Son imajda restore yok. COPY sadece /out. apt yok. Kalan şişkinlik aspnet temel katmanı — onu pinlerim, kesmem. 'Daha ince' diye alpine'a geçmek, glibc bekleyen native bağda kırıldı. İncelik, derleyiciyi taşımamaktır. Distro değişimi ikinci iş.
Node'da iki ci
Tek node_modules hem derler hem koşar sanmak, devDependency'yi üretime gömer. Typescript, test runner, linter. İki aşama: build'de tam npm ci, runtime ağacı için npm ci --omit=dev ayrı bir stage'te veya npm prune --omit=dev tek stage'in sonunda. Prune, aynı katmanda olmazsa önceki node_modules katmanı durur. Prune'u ayrı RUN yazmak, imajı küçültmez; yeni bir katman ekler, eskisi altta kalır.
FROM node:20.11.1-bookworm-slim AS deps
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
FROM deps AS build
COPY tsconfig.json ./
COPY src/ src/
RUN npm run build
FROM node:20.11.1-bookworm-slim AS prod-deps
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:20.11.1-bookworm-slim
WORKDIR /app
RUN useradd --system --uid 10001 app
USER app
COPY --from=prod-deps --chown=app:app /src/node_modules ./node_modules
COPY --from=build --chown=app:app /src/dist ./dist
CMD ["node", "dist/server.js"]
Üç ara oda gibi duruyor. Değil: deps cache'i, derleme, üretim ağacı. Son oda üç kopya alıyor, hiçbiri SDK değil. npm run build için gereken typescript prod-deps'te yok. Yanlışlıkla COPY --from=deps yapmak, birinci yazıdaki latest kadar sessiz bir şişme.
apt aynı RUN içinde biter
apt-get update ile apt-get install ayrı satırsa, update katmanı cache'ten gelir, install eski indeksi kullanır veya lists şişer. İkisini ve rm -rf /var/lib/apt/lists/* satırını tek RUN yaz. DEBIAN_FRONTEND=noninteractive ve --no-install-recommends varsayılanım. Recommends, 'bir paket daha' diye 80 MB getirir.
Native kütüphane runtime'da gerekiyorsa son katta kurulur. Build katında libpq-dev açıp son katta libpq5 unutmak, kutuyu ayağa kaldırır, ilk sorguda patlatır. Dev ve runtime paket adları ayrıdır. İkisini de pinli temel imajın üstüne, bilinçli yazarım.
Kopyalamadığım şeyler
Test projesi, appsettings.Development.json, editör config, README. COPY . . hepsini ister. COPY src/Api/ daraltır. Daraltmak ignore'un ikizidir. İkisini de yazmazsan multi-stage, şişmenin yarısını keser, diğer yarısı kaynak ağacı olarak durur.
dotnet publish -p:PublishTrimmed=true ayrı bir incelik. Trim, yansıma kullanan kütüphaneyi keser. Kesmeden önce publish log'una bak. Trim ile 180 MB'ı 90'a indirmek cazip, ilk istekte MissingMethodException pahalı. Önce stage ayrımı, sonra trim. Sırayı tersine çevirmek, ince ve bozuk bir kutu üretir.
- SDK son katta yok.
- Restore cache son katta yok.
- Kaynak ağacı son katta yok.
- apt lists aynı RUN içinde silinmiş.
- read-only koşu ayağa kalkıyor.
- USER root değil.
- ENTRYPOINT tek süreç, kabuk sarmalayıcı değil.
Son kontrol: docker run --rm --read-only --tmpfs /tmp ile kutu ayağa kalkıyor mu. Kalkıyorsa derleme artığına veya yazılabilir cache'e bel bağlamıyorsunuz. Kalkmıyorsa son kata gizlice bir workspace sızmıştır. Multi-stage bitmiş sayılmaz; sadece imaj numarası küçülmüştür. Küçük numara, doğru kutu demek değildir.
Ölçü: docker images ve docker history --no-trunc. Hangi satır 400 MB, o satır kopya mı, cache mi, sdk mi. 'Image büyük' teşhis değil. Teşhis bir katman satırı. O satırı son kata taşımayı bırak. Taşımazsan 180 MB tekrar 1.4 GB olur; sadece daha geç fark edilir.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap