← До програми курсу

Модуль 4 / 18

Dockerfile: відтворювана збірка застосунку

DOCKER

Зміст
01

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
02

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.

03

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.

04

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 не розкриваються.

05

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.

06

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, але права файлів і вимоги застосунку все одно треба перевірити.

07

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
08

8. Лабораторна робота: збираємо non-root web image

Створіть ізольований context у тимчасовому каталозі. Dockerfile запускає BusyBox httpd напряму під numeric non-root UID, а bind публікації обмежений loopback хоста. Потім дослідіть image config і runtime process.

terminal
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
' | sort
terminal
docker 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 id
terminal
docker 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 і з якої інструкції почалося перезбирання.

terminal
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 та залежні операції.

09

9. Діагностика: build завершився помилкою

Створіть окремий помилковий Dockerfile з невідомою інструкцією COPYY. Очікуваний failure збережено через || true, щоб наступні діагностичні команди виконалися під час копіювання блоку повністю.

terminal
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

10. Контроль, cleanup і шпаргалка

  • Чому COPY не читає шлях поза build context?
  • Чим RUN відрізняється від CMD?
  • Як docker run arguments впливають на CMD та ENTRYPOINT?
  • Чому EXPOSE не відкриває host port?
  • Чому ARG не підходить для secret?
  • Що саме інвалідує COPY layer?
  • Чим --pull відрізняється від --no-cache?
  • Чому видалення secret у наступному RUN недостатньо?
terminal
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 без небезпечних обходів.