Модуль 4 / 18
Dockerfile: відтворювана збірка застосунку
Зміст
1. Цілі та результат модуля
Після модуля ви зможете читати Dockerfile як декларацію збирання, проєктувати мінімальний build context, відрізняти build-time від runtime, обирати exec form і перевіряти створений image за metadata та запуском.
- Dockerfile задає відтворюваний процес створення image
- build context є входом збирання та межею для COPY
- порядок інструкцій визначає cache invalidation
- RUN виконується під час збирання, CMD і ENTRYPOINT — під час запуску
- ARG, ENV і secrets мають різний життєвий цикл
- USER та явні runtime metadata входять до контракту image
2. Теорія: Dockerfile, frontend і build context
Dockerfile — вхід для Dockerfile frontend BuildKit, а не shell script. Parser directive # syntax=docker/dockerfile:1 обирає актуальний стабільний синтаксис до першої інструкції. Кожна інструкція створює операцію build graph; результат залежить від base image, файлів context, аргументів та зовнішніх джерел.
Останній аргумент docker build задає context. COPY не може довільно читати файли вище його кореня. Передавання кореня великого repository сповільнює обчислення checksums і розширює ризик випадково включити credentials, caches та artifacts. .dockerignore виключає непотрібні шляхи до надсилання context builder.
3. Теорія: призначення основних інструкцій
FROM починає stage і визначає base image. WORKDIR явно задає каталог для наступних RUN, COPY, CMD та ENTRYPOINT. COPY переносить локальні файли, named context або артефакти stage; ADD має додаткові можливості й не потрібен, коли достатньо COPY.
RUN виконує команду під час build і фіксує результат у layer. ENV зберігається в image config та доступний container; ARG існує під час build, але не є безпечним каналом для secret. LABEL зберігає metadata. EXPOSE документує порт, але не публікує його на host.
USER задає користувача для наступних RUN і runtime. VOLUME оголошує mount point, але ускладнює подальші зміни цього шляху й не створює керованої стратегії backup. HEALTHCHECK, STOPSIGNAL та shell глибше розглядаються в модулях про runtime і health.
4. Теорія: RUN, CMD, ENTRYPOINT і форми запуску
RUN змінює image під час збирання. CMD задає default command або default arguments; альтернативні аргументи docker run замінюють CMD. ENTRYPOINT задає executable контейнера, а CMD зручно використовувати як його змінні аргументи. У Dockerfile ефективний лише останній CMD і останній ENTRYPOINT.
Exec form — JSON-масив із подвійними лапками — запускає executable напряму, не виконує shell expansion і робить доставку сигналів передбачуванішою. Shell form проходить через /bin/sh -c: вона зручна для pipelines і змінних, але shell стає додатковим процесом. Змінні в exec form без явного shell не розкриваються.
5. Теорія: відтворюваність і dependency pinning
Однаковий Dockerfile не гарантує однакові bytes, якщо tag base image, package indexes або завантажувані URL змінні. Для строгої ідентичності base image фіксують digest, dependencies — lockfile або версією, а мережу й час враховують як inputs. Водночас digest pinning потребує процесу регулярних security updates.
--pull перевіряє оновлення base image, а --no-cache забороняє повторне використання build cache; це різні операції. Для apt update та apt install використовують одну RUN-інструкцію, щоб cache не зберіг застарілий index. Потрібні пакети встановлюють із --no-install-recommends, а package metadata видаляють у тому самому layer.
6. Теорія: cache ordering, права та secrets
Build cache перевикористовується, доки операція та її inputs не змінилися. Стабільні й дорогі кроки розміщують раніше, часто спочатку копіюючи lockfile та встановлюючи dependencies, а потім source. COPY . . на початку робить будь-яку зміну причиною перезбирання всіх наступних кроків.
Secret не можна передавати через ARG, ENV, COPY або записувати й видаляти в наступному RUN: він може лишитися в metadata, provenance, cache або нижньому layer. Для secrets збирання використовують BuildKit secret mount. Numeric UID/GID зменшують залежність від вмісту /etc/passwd, але права файлів і вимоги застосунку все одно треба перевірити.
7. Методичні вказівки
- Починайте з мінімального context і явного .dockerignore
- Використовуйте COPY замість ADD без спеціальної причини
- Фіксуйте base image та dependencies згідно з політикою оновлень
- Відокремлюйте build configuration від runtime configuration
- Не кладіть credentials до ARG, ENV, layers і repository
- Віддавайте перевагу exec form для ENTRYPOINT та CMD
- Переходьте на non-root USER після операцій, що потребують root
- Перевіряйте image inspect, history і фактичний запуск, а не лише успішний build
8. Лабораторна робота: збираємо non-root web image
Створіть ізольований context у тимчасовому каталозі. Dockerfile запускає BusyBox httpd напряму під numeric non-root UID, а bind публікації обмежений loopback хоста. Потім дослідіть image config і runtime process.
mkdir -p /tmp/docker-module-04
printf '%s
' '<!doctype html>' '<html lang="en"><meta charset="utf-8"><title>Docker module 4</title><h1>reproducible image</h1></html>' > /tmp/docker-module-04/index.html
printf '%s
' '.git' '.env' '*.log' 'node_modules' 'Dockerfile.bad' > /tmp/docker-module-04/.dockerignore
printf '%s
' '# syntax=docker/dockerfile:1' 'FROM busybox:1.37' 'ARG BUILD_VERSION=dev' 'LABEL org.opencontainers.image.title="docker-module-04"' 'LABEL org.opencontainers.image.version="$BUILD_VERSION"' 'WORKDIR /srv/www' 'COPY --chown=65532:65532 index.html ./' 'USER 65532:65532' 'EXPOSE 8080' 'STOPSIGNAL SIGTERM' 'ENTRYPOINT ["httpd","-f","-p","8080","-h","/srv/www"]' > /tmp/docker-module-04/Dockerfile
find /tmp/docker-module-04 -maxdepth 1 -type f -printf '%f %s bytes
' | sortdocker build --pull --build-arg BUILD_VERSION=1.0.0 --tag docker-m4:1.0 /tmp/docker-module-04
docker image inspect docker-m4:1.0 --format '{{json .Config}}' | jq '{User,WorkingDir,Entrypoint,Cmd,ExposedPorts,Labels,StopSignal}'
docker image history --no-trunc docker-m4:1.0
docker run --detach --name docker-m4-web --publish 127.0.0.1:18084:8080 docker-m4:1.0
curl --fail --silent --show-error http://127.0.0.1:18084/
docker top docker-m4-web
docker exec docker-m4-web iddocker inspect docker-m4-web --format 'path={{.Path}} args={{json .Args}} user={{.Config.User}}'
docker create --name docker-m4-override docker-m4:1.0 -v
docker inspect docker-m4-override --format 'path={{.Path}} args={{json .Args}} status={{.State.Status}}'
docker container rm docker-m4-override
docker run --rm --entrypoint sh docker-m4:1.0 -c 'printf "uid=%s workdir=%s\n" "$(id -u)" "$PWD"'
docker container stop docker-m4-web
docker container rm docker-m4-webПовторіть build без змін, потім змініть лише index.html. У виводі збирання позначте, які операції були cached і з якої інструкції почалося перезбирання.
docker build --tag docker-m4:cache-a /tmp/docker-module-04
docker build --tag docker-m4:cache-b /tmp/docker-module-04
docker image history docker-m4:cache-b
printf '%s
' '<!doctype html>' '<html lang="en"><meta charset="utf-8"><title>Docker module 4</title><h1>changed source</h1></html>' > /tmp/docker-module-04/index.html
docker build --tag docker-m4:changed /tmp/docker-module-04
docker image history docker-m4:changedКритерій: endpoint відповідає, процес працює не від root, metadata збігається з Dockerfile, повторне збирання використовує cache, а зміна source інвалідує COPY та залежні операції.
9. Діагностика: build завершився помилкою
Створіть окремий помилковий Dockerfile з невідомою інструкцією COPYY. Очікуваний failure збережено через || true, щоб наступні діагностичні команди виконалися під час копіювання блоку повністю.
printf '%s
' '# syntax=docker/dockerfile:1' 'FROM busybox:1.37' 'WORKDIR /srv/www' 'COPYY index.html ./' 'CMD ["httpd","-f","-p","8080","-h","/srv/www"]' > /tmp/docker-module-04/Dockerfile.bad
docker build --file /tmp/docker-module-04/Dockerfile.bad --tag docker-m4:broken /tmp/docker-module-04 || true
docker buildx imagetools inspect busybox:1.37
docker version --format 'client={{.Client.Version}} server={{.Server.Version}}'
docker info --format 'driver={{.Driver}} root={{.DockerRootDir}}'- спочатку знайти Dockerfile і підтвердити обраний --file
- перевірити корінь context та дію .dockerignore
- читати першу причинну помилку build output, а не останній рядок
- розділити parser, context transfer, pull, RUN та export failure
- перевірити proxy, CA й authentication лише якщо падає мережевий крок
- звірити доступні flags із версіями Docker CLI та buildx
- не лікувати помилку вимкненням BuildKit або запуском privileged builder
10. Контроль, cleanup і шпаргалка
- Чому COPY не читає шлях поза build context?
- Чим RUN відрізняється від CMD?
- Як docker run arguments впливають на CMD та ENTRYPOINT?
- Чому EXPOSE не відкриває host port?
- Чому ARG не підходить для secret?
- Що саме інвалідує COPY layer?
- Чим --pull відрізняється від --no-cache?
- Чому видалення secret у наступному RUN недостатньо?
docker container rm --force docker-m4-web 2>/dev/null || true
docker image rm docker-m4:1.0 docker-m4:cache-a docker-m4:cache-b docker-m4:changed 2>/dev/null || true
rm -f /tmp/docker-module-04/Dockerfile /tmp/docker-module-04/Dockerfile.bad /tmp/docker-module-04/.dockerignore /tmp/docker-module-04/index.html
rmdir /tmp/docker-module-04Модуль засвоєно, якщо ви пояснюєте кожен layer і runtime metadata, прогнозуєте cache invalidation, збираєте non-root image та локалізуєте build failure без небезпечних обходів.