Пошук уроків, статей та іншого контенту
Навчитеся впорядковувати інструкції, зменшувати розмір образів і використовувати кеш шарів для швидших збірок.
Dockerfile складається з інструкцій, а під час збірки Docker створює для них кешовані шари. Середовище наступного шару містить результат попередніх інструкцій.
Наприклад:
FROM node:22-bookworm-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "src/server.js"]У цьому Dockerfile окремі інструкції формують різні шари:
базовий образ FROM;
робочий каталог WORKDIR;
копіювання файлів залежностей;
встановлення залежностей;
копіювання коду застосунку;
команда запуску.
Docker зберігає результат успішної збірки та може повторно використати його під час наступної збірки.
docker build -t my-app .Якщо після цього змінити лише файл src/server.js, Docker повторно виконає інструкцію COPY . . і всі інструкції після неї. Шар із npm ci буде взято з кешу.
Docker перевіряє інструкції послідовно, від верхньої до нижньої.
Кеш для інструкції може бути використаний, якщо:
сама інструкція не змінилася;
попередні шари не змінилися;
файли, які копіює COPY або ADD, мають той самий вміст.
Важливо, що після першої інструкції без кешу Docker зазвичай не може використати кеш для наступних інструкцій. Тому порядок команд безпосередньо впливає на час збірки.
FROM node:22-bookworm-slim
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "src/server.js"]У цьому випадку зміна будь-якого файлу в контексті збірки може зробити шар COPY . . недійсним. Після цього Docker знову виконає npm ci, навіть якщо список залежностей не змінився.
FROM node:22-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
CMD ["node", "src/server.js"]Тепер npm ci буде повторно виконано лише тоді, коли зміниться package.json або package-lock.json.
Загальне правило:
Спочатку розміщуйте рідко змінювані інструкції.
Потім копіюйте файли залежностей.
Встановлюйте залежності.
Наприкінці копіюйте код, який змінюється найчастіше.
Структура проєкту:
my-app/
├── Dockerfile
├── .dockerignore
├── package.json
├── package-lock.json
└── src/
└── server.jsПриклад Dockerfile:
FROM node:22-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
# Спочатку копіюємо лише файли, що описують залежності
COPY package.json package-lock.json ./
# Встановлюємо тільки production-залежності
RUN npm ci --omit=dev && npm cache clean --force
# Код змінюється частіше, тому копіюємо його після залежностей
COPY src ./src
EXPOSE 3000
CMD ["node", "src/server.js"]Приклад package.json:
{
"name": "docker-cache-example",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node src/server.js"
},
"dependencies": {
"express": "^4.21.2"
}
}Приклад src/server.js:
import express from "express";
const app = express();
const port = process.env.PORT || 3000;
app.get("/", (_request, response) => {
response.json({ message: "Застосунок працює" });
});
app.listen(port, () => {
console.log(`Сервер запущено на порту ${port}`);
});Збірка та запуск:
docker build -t docker-cache-example .
docker run --rm -p 3000:3000 docker-cache-exampleПісля зміни src/server.js шар зі встановленням залежностей буде взято з кешу. Це особливо помітно у великих проєктах, де встановлення залежностей займає багато часу.
COPYІнструкція COPY впливає на кешування всіх наступних шарів. Тому не завжди варто копіювати весь проєкт однією командою.
Замість цього розділіть файли за частотою змін:
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
COPY public ./publicЯкщо зміниться файл у src, залежності не буде встановлено повторно. Якщо зміниться package-lock.json, Docker правильно перебудує шар із npm ci.
Не слід копіювати файли, які не потрібні для збірки або запуску. Наприклад:
COPY . .може додати до контексту та образу:
локальні залежності;
журнали;
документацію;
файли редактора;
результати попередніх збірок;
секрети.
.dockerignore.dockerignore визначає файли, які не передаються в контекст збірки та не враховуються під час COPY . ..
Приклад:
node_modules
npm-debug.log
.git
.gitignore
Dockerfile
.dockerignore
.env
coverage
distФайл .dockerignore допомагає:
зменшити контекст збірки;
прискорити передавання файлів Docker daemon;
не створювати зайві зміни в кеші;
випадково не додати секрети до образу.
.dockerignore не замінює правильний порядок інструкцій. Навіть із ним краще спочатку копіювати файли залежностей, а потім код.
Кожна інструкція RUN, COPY і ADD може створити новий шар. Більша кількість шарів не завжди є проблемою, але зайві дані в них збільшують образ і ускладнюють кешування.
Невдалий варіант:
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*Проблема полягає в тому, що видалення файлів у наступному шарі не прибирає їх із попереднього шару. Дані все одно можуть залишитися в історії образу.
Кращий варіант:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*Усі операції виконуються в одному шарі. Тимчасові файли видаляються до того, як шар буде збережено.
Для Debian- та Ubuntu-подібних образів apt-get update і apt-get install варто виконувати в одній інструкції. Це зменшує ризик використання застарілого індексу пакетів.
Надмірне об'єднання також може погіршити кешування:
RUN npm ci && npm run build && cp config.json /app/Зміна будь-якої частини цієї логіки змусить повторно виконати весь RUN. Об'єднувати варто операції, які мають спільний життєвий цикл і тимчасові файли, а не всі команди без винятку.
Вибір базового образу значно впливає на підсумковий розмір:
FROM node:22має більше системних компонентів, ніж:
FROM node:22-bookworm-slimВикористовуйте компактний образ, якщо застосунку не потрібні додаткові інструменти операційної системи.
Проте дуже мінімальні образи можуть мати обмеження:
відсутні стандартні утиліти;
відрізняється менеджер пакетів;
деякі native-залежності можуть потребувати додаткового налаштування;
діагностика контейнера може бути складнішою.
Тому слід обирати не найменший можливий образ, а найменший образ, який надійно підтримує застосунок.
Для production-застосунку часто не потрібні dev-залежності:
RUN npm ci --omit=devТак образ міститиме лише залежності, необхідні під час запуску.
Кеш самого npm також не потрібен у фінальному образі:
RUN npm ci --omit=dev \
&& npm cache clean --forceОчищення кешу в тій самій інструкції важливе. Якщо очищення виконати в окремому шарі, дані з попереднього шару можуть залишитися в образі.
Docker BuildKit підтримує кешовані директорії для інструментів пакетного менеджера. Такий кеш зберігається між збірками, але не потрапляє до фінального шару образу.
Приклад для npm:
# syntax=docker/dockerfile:1
FROM node:22-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
COPY src ./src
CMD ["node", "src/server.js"]Кеш монтується лише на час виконання RUN. Це може прискорити повторні встановлення залежностей, особливо після зміни lock-файлу.
Збірка з BuildKit:
DOCKER_BUILDKIT=1 docker build -t docker-cache-example .У сучасних версіях Docker BuildKit зазвичай використовується за замовчуванням.
Під час збірки повідомлення CACHED означає, що Docker використав готовий результат:
[1/5] FROM node:22-bookworm-slim
=> CACHED
[2/5] WORKDIR /app
=> CACHED
[3/5] COPY package.json package-lock.json ./
=> CACHED
[4/5] RUN npm ci --omit=dev
=> CACHEDЩоб примусово проігнорувати кеш і виконати всі інструкції заново:
docker build --no-cache -t docker-cache-example .Це корисно для перевірки того, що Dockerfile справді може виконатися з чистого стану.
Для перегляду створених образів:
docker images docker-cache-exampleДля перегляду шарів образу:
docker history docker-cache-exampleКоманда покаже інструкції, розмір відповідних шарів і порядок їх створення.
Якщо застосунку потрібна окрема команда збірки, можна розділити середовище збірки та середовище запуску. Це дозволяє не переносити інструменти й dev-залежності до фінального образу.
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
RUN npm run build
FROM node:22-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]У цьому прикладі:
стадія build містить інструменти та всі залежності для компіляції;
стадія runtime містить лише production-залежності та результат збірки;
файли, потрібні тільки під час компіляції, не потрапляють до фінального образу.
Такий підхід особливо корисний для TypeScript, frontend-застосунків і проєктів, які генерують папку dist.
COPY . .
RUN npm ciЗміна будь-якого файлу змусить повторно встановлювати залежності. Спочатку копіюйте маніфести та lock-файл.
RUN apt-get update
RUN apt-get install -y curlКраще поєднати ці команди та видалити індекси пакетів у тому самому шарі.
COPY . .Якщо .env не додано до .dockerignore, він може потрапити до образу. Секрети не повинні зберігатися у файлах, які копіюються під час збірки.
--no-cache для кожної збіркиdocker build --no-cache -t my-app .Це вимикає головну перевагу кешування та змушує повторно виконувати всі кроки. Використовуйте цей параметр лише для діагностики або повної перевірки чистої збірки.
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*Так тимчасові файли можуть залишитися в попередньому шарі. Пов'язані операції очищення слід виконувати в одному RUN.
Якщо локально вже існують node_modules або dist, команда COPY . . може перенести їх до образу. Додавайте такі каталоги до .dockerignore або копіюйте лише потрібні директорії.
Docker кешує результати інструкцій Dockerfile і перевіряє їх зверху вниз.
Після першої зміненої інструкції наступні шари зазвичай перебудовуються.
Файли, що змінюються рідко, потрібно розміщувати ближче до початку Dockerfile.
package.json і lock-файл варто копіювати перед кодом застосунку.
.dockerignore зменшує контекст збірки та запобігає копіюванню зайвих файлів.
Пов'язані команди встановлення й очищення слід виконувати в одному RUN.
Для production корисно виключати dev-залежності та кеш пакетного менеджера.
Компактний базовий образ і multi-stage build допомагають зменшити фінальний образ.
docker history допомагає перевірити, які шари займають найбільше місця.