Три змістовні шари: базовий образ, встановлений curl, скопійований файл
Той самий Dockerfile як шари
FROM ubuntu:24.04базові шари · ~80 МБ
→
RUN apt-get …шар: +curl і залежності
→
COPY app.shшар: +1 файл
→
CMDметадані, без шару
docker history покаже кожен із них окремим рядком із розміром
Кеш збірки: чому порядок важливий
Docker кешує шар за хешем інструкції та вмісту її вхідних файлів
Збіг хеша — шар береться з кешу, інструкція не виконується
Один змінений шар інвалідує ВСІ наступні — кеш ламається каскадно
Правило: рідкозмінне (залежності) — на початок, мінливе (код) — у кінець
Як Docker вирішує: кеш чи перебудова
1
Хеш інструкції + входутекст RUN або вміст файлів COPY/ADD
2
Є такий шар у кеші?так — беремо готовий, далі до наступної
3
Перша розбіжністьінструкція виконується заново → новий шар
4
Каскадусі наступні шари перебудовуються без перевірки
5
Висновокчим нижче зміна в Dockerfile — тим дешевша збірка
Кеш шарів: ціна одного рядка не на своєму місці
Правило: спершу те, що змінюється рідко (маніфест залежностей), потім код
Порядок інструкцій і кеш
dockerfile
# погано: COPY усього коду перед install ламає кеш на кожній зміні кодуCOPY . .
RUN npm install
# добре: спершу залежності — кешуються, доки package.json не змінивсяCOPY package*.json .
RUN npm install
COPY . .
COPY package.json окремо від решти коду зберігає кеш npm install
BuildKit і кеш-монтування
BuildKit — сучасний бекенд збірки Docker (типовий з версії 23+)
--mount=type=cache зберігає каталог МІЖ збірками, навіть коли шар перебудовано
Класика: кеш реєстру cargo, npm чи pip, що не потрапляє в шар
docker build --target=<stage> зупиняє збірку на проміжному етапі
Кеш-монтування на практиці
dockerfile
FROM rust:1.80AS build
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/root/.cargo/registry \
cargo build --release
Кеш cargo-реєстру переживає перебудову шару, навіть якщо код змінився
Об'єднана файлова система (*OverlayFS*): чотири каталоги
lowerdirшари образу, лише читання; може бути кілька
upperdirшар запису контейнера; порожній на старті
workdirслужбовий каталог для атомарних операцій
mergedоб'єднаний вигляд — корінь ФС для процесу
Запис у контейнер ніколи не змінює образ — лише upperdir
OverlayFS: шари, верхній рівень і whiteout
Видалення файлу в контейнері не торкається образу — у верхньому шарі з'являється whiteout
Copy-on-write в OverlayFS
1
Читанняфайл береться з найвищого шару, де він є
2
Зміна файлу з lowerdircopy-up: копія в upperdir, зміна там
3
Новий файл (.pixel_was_here)одразу в upperdir, шари образу не чіпає
4
Видалення файлу образуwhiteout-позначка в upperdir; місце не звільняється
5
docker rmupperdir відкинуто — образ незайманий
Живе демо: docker history
docker build -t demo:1 .
docker history demo:1
Показати розмір кожного шару окремо
docker inspect demo:1 | grep -A5 RootFS — список шарів за хешами
Очікувано: Студенти бачать: "вага" образу — це сума конкретних шарів, і видно, яка саме інструкція додала найбільше мегабайтів
docker inspect: digests і шари
bash
docker inspect demo:1 | grep-A5 RootFS
docker pull nginx@sha256:8f9c...
# digest — SHA-256 маніфесту: незмінний ідентифікатор вмісту,# на відміну від тегу (latest, 1.27), який можна перепризначити
Digest ≠ тег: тег можна перезаписати, digest — криптографічно незмінний
Тег ↔ digest
Тег (nginx:1.27, latest)
Зручне людське ім'я
Змінний: власник може перепризначити
latest — просто тег за замовчуванням
Для розробки й читабельності
vs
Digest (sha256:8f9c…)
SHA-256 маніфесту образу
Незмінний: інший вміст — інший хеш
Гарантує той самий вміст завтра
Для production і відтворюваних збірок
docker pull nginx@sha256:… — точний вміст, а не «поточний latest»
Вага образу в цифрах
1.5 ГБ
образ з компілятором усередині
rust:1.80 + вихідний код + кеш
~80 МБ
ubuntu:24.04
без ядра, локалей і документації
~5 МБ
alpine
musl libc + apk + shell
0 Б
scratch
жодного шару — лише твій бінарник
Проблема: інструменти збірки потрібні лише під час збірки, а лишаються назавжди
Багатоетапна збірка (*multi-stage build*)
FROM rust AS buildкомпілятор, cargo, код
→
cargo build --releaseартефакт: один бінарник
→
COPY --from=buildпереносимо лише /app
→
FROM scratchфінальний образ: кілька МБ
Кожен FROM — окремий ізольований етап; проміжні у фінальний образ не потрапляють
Що саме переходить у фінальний образ
Перевірка на розумність: чи є у фінальному образі компілятор? Якщо так — етапів мало
Багатоетапна збірка
dockerfile
FROM rust:1.80AS build
WORKDIR /src
COPY . .
RUN cargo build --release
FROM scratch
COPY --from=build /src/target/release/app /app
ENTRYPOINT ["/app"]
Компілятор Rust лишається в build-етапі; у фінальному образі — лише бінарник
Базовий образ для фінального етапу
База
Розмір
Shell / libc
Коли обирати
scratch
0 Б
немає / немає
статично злінкований бінарник (Rust, Go)
distroless
~2–20 МБ
немає / glibc
динамічний бінарник без shell
alpine
~5 МБ
sh / musl
потрібен shell або apk для діагностики
ubuntu:24.04
~80 МБ
bash / glibc
повна сумісність, apt
musl (alpine) інколи поводиться інакше за glibc — не завжди безкоштовна заміна
Лише інструкції RUN, COPY та ADD створюють шари. Інші інструкції створюють тимчасові проміжні образи й не збільшують розмір збірки.
— Docker Docs, «Best practices for writing Dockerfiles»
Наперед: кілька контейнерів разом
Сьогодні — один образ, один контейнер
Далі в лабораторних: два контейнери, ізольована мережа, спільний том, секрети
Питання наперед: як контейнери знаходять один одного за іменем, а не за IP?
Наперед: мережа між контейнерами (ЛР3)
Деталі — на наступній лекції та в ЛР3; тут важливо: імена, а не IP, і -p як єдина хвіртка
Підводні камені: шари, кеш і збірка
COPY . . на початку Dockerfile ламає кеш при будь-якій зміні в репозиторії
Без .dockerignore контекст збірки роздувається: node_modules, .git
scratch несумісний з динамічним лінкуванням — потрібен static linking
Багато дрібних RUN замість одного через && множить шари й вагу
Підсумок
Образ = стек незмінних, адресованих за вмістом шарів; контейнер додає шар для запису
Порядок інструкцій у Dockerfile визначає ефективність кешу збірки
Багатоетапна збірка + scratch дають мінімальний образ виконання
docker history — інструмент, щоб побачити "вагу" кожної інструкції
🥚 У прикладі OverlayFS новий файл названо .pixel_was_here — він існує лише в upperdir і зникає разом із контейнером. Цей рядок у файловій системі ще трапиться.
VTFK · TERMLINK · L03← → · space · f — на весь екран