Imageی که ۱٫۲ گیگابایت حجم دارد فقط فضای دیسک را هدر نمیدهد. هر بار که HPA یک Pod جدید روی Node تازه میسازد، باید همین ۱٫۲ گیگابایت را Pull کند. هر اسکن امنیتی هم صدها آسیبپذیری در ابزارهایی پیدا میکند که اصلاً در Production لازم نیستند. در این مقاله روشهایی را میگویم که معمولاً حجم Image را چند برابر کم و امنیتش را بیشتر میکنند.
۱. Multi-stage Build
ایده ساده است: یک Stage برای Build با همهی ابزارها (SDK، کامپایلر، پکیجمنیجر) و یک Stage نهایی که فقط خروجی Build را دارد.
نمونه برای .NET:
# ---- build stage ----
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY *.sln ./
COPY src/Api/Api.csproj src/Api/
RUN dotnet restore src/Api/Api.csproj
COPY . .
RUN dotnet publish src/Api/Api.csproj -c Release -o /app --no-restore
# ---- runtime stage ----
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled
WORKDIR /app
COPY --from=build /app .
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "Api.dll"]
و برای Node.js:
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
۲. ترتیب دستورها = سرعت Build
Docker هر دستور را یک Layer در نظر میگیرد و اگر ورودیاش تغییر نکرده باشد، از Cache استفاده میکند. به همین دلیل:
# ❌ any code change invalidates the dependency layer
COPY . .
RUN npm ci
# ✅ dependencies are re-installed only when package files change
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
قانون کلی: چیزهایی که کمتر عوض میشوند، بالاتر.
۳. فایل .dockerignore را فراموش نکنید
بدون آن، کل پوشه (از جمله .git، node_modules و فایلهای .env) به Docker فرستاده میشود:
.git
node_modules
bin
obj
dist
*.md
.env*
docker-compose*.yml
۴. هرگز با Root اجرا نکنید
اگر مهاجم از طریق یک آسیبپذیری وارد کانتینر شود، کاربر Root کار او را برای فرار از کانتینر بسیار سادهتر میکند. Imageهای رسمی معمولاً کاربر آماده دارند (node در Node.js و $APP_UID در .NET 8 به بعد). در Kubernetes هم آن را اجباری کنید:
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: [ALL] }
۵. Base Image مناسب انتخاب کنید
| نوع Image | حجم تقریبی | Shell دارد؟ | مناسب برای |
|---|---|---|---|
| Debian / Ubuntu کامل | ۲۰۰ تا ۸۰۰ مگابایت | بله | Stage مربوط به Build |
| Alpine | ۵ تا ۵۰ مگابایت | بله | بیشتر سرویسها |
| Distroless (Google) | ۲ تا ۳۰ مگابایت | خیر | Go، Java، Node در Production |
| Chiseled (Ubuntu، مخصوص .NET) | حدود ۱۰۰ مگابایت با Runtime | خیر | سرویسهای .NET |
Imageهای بدون Shell امنترند، چون مهاجم ابزاری برای کار ندارد. ولی Debug کردنشان سختتر است. برای عیبیابی از kubectl debug با یک کانتینر موقت استفاده کنید:
kubectl debug -it pod/api-7d9f -n production --image=busybox:1.36 --target=api
۶. نسخه را ثابت کنید
FROM node:latest یعنی Build امروز و Build ماه بعد از دو Image پایهی متفاوت ساخته میشوند. حداقل نسخهی Minor را مشخص کنید (node:22.11-alpine). برای اطمینان کامل از Digest استفاده کنید:
FROM node:22-alpine@sha256:<digest>
ابزارهایی مثل Renovate این Digestها را خودکار بهروز میکنند و برایش Merge Request میسازند.
۷. Cache بین Buildها در CI
Runnerهای CI معمولاً Cache لایهها را بین Jobها نگه نمیدارند. با BuildKit، Cache را در خود Registry ذخیره کنید:
docker-image:
image: docker:27
services: [docker:27-dind]
script:
- docker buildx create --use
- docker buildx build
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE:buildcache
--cache-to type=registry,ref=$CI_REGISTRY_IMAGE:buildcache,mode=max
-t "$IMAGE" --push .
در پروژههایی با دهها میکروسرویس، همین تغییر زمان Build را بهطور محسوسی کم میکند.
۸ تا ۱۰. سه عادت کوچک دیگر
HEALTHCHECKیا بهتر از آن، Probeهای Kubernetes.- یک Process در هر کانتینر: Nginx و برنامه را در یک کانتینر با supervisord اجرا نکنید.
- اسکن خودکار با Trivy در Pipeline، تا هر Image قبل از رسیدن به Production بررسی شود.
نتیجهی واقعی
| قبل | بعد | |
|---|---|---|
| Base Image | sdk کامل |
aspnet Chiseled |
| حجم | حدود ۹۰۰ مگابایت | حدود ۱۲۰ مگابایت |
| آسیبپذیریهای گزارششده توسط Trivy | دهها مورد | انگشتشمار |
| کاربر | root | غیر Root |
اعداد بالا تقریبیاند و به پروژه بستگی دارند، اما الگو همیشه همین است: Image کوچکتر، سریعتر و امنتر است.
جمعبندی
بهینهسازی Image یکی از کمهزینهترین بهبودهایی است که میتوانید در زنجیرهی DevOps انجام دهید: چند خط تغییر در Dockerfile، و نتیجهاش Deploy سریعتر، Scale سریعتر و گزارش امنیتی تمیزتر است.