← К программе курса

Модуль 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, зависимости — 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, права и секреты

Build cache переиспользуется, пока операция и её входы не изменились. Стабильные и дорогие шаги размещают раньше, часто сначала копируя lockfile и устанавливая зависимости, а затем source. COPY . . в начале делает любое изменение причиной пересборки всех следующих шагов.

Секрет нельзя передавать через ARG, ENV, COPY или записывать и удалять в следующем RUN: он может остаться в metadata, provenance, cache или нижнем layer. Для секретов сборки используют 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

Модуль освоен, если вы объясняете каждый слой и runtime metadata, прогнозируете cache invalidation, собираете non-root image и локализуете build failure без опасных обходов.