Dockerfile, за який не соромно: multi-stage, кеш шарів і малий образ
Як писати Dockerfile, щоб образ збирався за секунди, важив у рази менше і не тягнув у продакшен компілятори, кеші й секрети.
Образ на 1.4 ГБ, який перезбирається 8 хвилин після зміни одного рядка коду, — типова картина в проєктах, де Dockerfile писали «аби запустилось». Виправити це зазвичай можна за годину, якщо розуміти, як працюють шари і кеш.
Як працює кеш шарів
Кожна інструкція (RUN, COPY, ADD) створює шар. При перезбірці Docker використовує кеш, доки інструкція і її вхідні файли не змінились. Щойно один шар змінився — усі наступні перезбираються.
# ❌ Будь-яка зміна коду інвалідовує кеш → npm ci щоразу заново
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]# ✅ Залежності змінюються рідко — копіюємо їх окремо і раніше
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]Правило: що рідше змінюється — то вище в Dockerfile.
Multi-stage: збираємо в одному образі, запускаємо в іншому
Компілятори, dev-залежності й вихідники потрібні лише для збірки. У фінальний образ потрапляє тільки результат.
# syntax=docker/dockerfile:1
# --- Збірка ---
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/app ./cmd/api
# --- Запуск ---
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]Результат: замість ~900 МБ (образ з Go) — 10–20 МБ, без shell і пакетного менеджера. Менше розмір — швидший деплой і менше вразливостей для сканера.
Кеш-маунти BuildKit
Навіть з правильним порядком шарів npm ci чи pip install після зміни залежностей качає все заново. Кеш-маунт зберігає кеш пакетного менеджера між збірками, не додаючи його в образ:
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt.dockerignore — обов'язково
Без нього в контекст збірки потрапляє все: node_modules, .git, локальні .env. Це повільно і небезпечно.
.git
node_modules
**/__pycache__
.env
.env.*
!.env.example
*.log
coverage
distОбережно
COPY . .без.dockerignoreможе запекти ваш.envз паролями прямо в образ. Навіть якщо пізніше видалити файл черезRUN rm, він залишиться в попередньому шарі й буде доступний будь-кому, хто завантажить образ.
Секрети під час збірки
Якщо для збірки потрібен токен (приватний npm-реєстр, Git), не передавайте його через ARG чи ENV — вони зберігаються в історії образу. Використовуйте secret-маунти:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --omit=devdocker build --secret id=npmrc,src=$HOME/.npmrc -t app .Дрібниці, які мають значення
- Фіксуйте версії базових образів:
node:22.11-alpineзамістьnode:latest. Для повної відтворюваності — за digest (@sha256:...). - Об'єднуйте команди встановлення і чистіть кеш в одному
RUN:RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && rm -rf /var/lib/apt/lists/* - Не запускайте від root:
USER node(є в офіційних Node-образах) або створіть свого користувача. - Exec-форма
CMD ["node", "server.js"]замістьCMD node server.js— тоді процес отримує сигналиSIGTERMі коректно завершується. - HEALTHCHECK для сервісів, яким це потрібно в Compose чи Swarm.
Як перевірити результат
docker images app # розмір
docker history app:latest # які шари скільки важать
docker build --progress=plain . # що бере з кешу (CACHED), а що збираєДля глибшого аналізу шарів зручний інструмент dive: він показує, які файли додав кожен шар, і знаходить «мертву вагу».
Чому RUN rm -rf /tmp/build у окремому шарі не зменшує розмір образу?
Шари лише додаються один над одним. Видалення в новому шарі приховує файли, але вони фізично залишаються в попередньому шарі образу. Щоб файли не потрапили в образ, їх треба видаляти в тому самому RUN, де вони з'явились, або використати multi-stage збірку.
Збережіть статтю собі
Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.
Увійти через Google Інші способи входу