Вопрос 4 · Раздел 14

Какие основные инструкции используются в Dockerfile?

Dockerfile состоит из инструкций, каждая из которых создаёт новый слой в образе. Вот основные из них:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Основные инструкции

Dockerfile состоит из инструкций, каждая из которых создаёт новый слой в образе. Вот основные из них:

Инструкции определения среды

Инструкция Что делает Пример
FROM Задаёт базовый образ FROM openjdk:17-slim
WORKDIR Устанавливает рабочую директорию WORKDIR /app
ENV Устанавливает переменные окружения ENV APP_PORT=8080

Инструкции работы с файлами

Инструкция Что делает
COPY Копирует файлы с хоста в образ
ADD Как COPY, но умеет распаковывать архивы

Инструкции выполнения команд

Инструкция Что делает
RUN Выполняет команду при сборке образа
CMD Команда по умолчанию при запуске контейнера
ENTRYPOINT Основная команда запуска контейнера

Инструкции настройки

Инструкция Что делает
EXPOSE Документирует порт приложения
USER Указывает пользователя для запуска

Простой пример

FROM openjdk:17-jdk-slim       # Базовый образ
WORKDIR /app                    # Рабочая директория
COPY myapp.jar app.jar          # Копируем jar-файл
EXPOSE 8080                     # Порт (документация)
ENTRYPOINT ["java", "-jar", "app.jar"]  # Команда запуска

COPY vs ADD

  • COPY – предпочтительный вариант (95% случаев).
  • ADD – если нужна автораспаковка tar-архивов или скачивание по URL (но для URL лучше RUN curl).

Exec form ["cmd", "arg"] – команда запускается напрямую. Shell form cmd arg – через оболочку /bin/sh -c. ENV – переменная доступна и при сборке, и в запущенном контейнере. ARG – только при сборке.

Что запомнить

  • FROM — всегда первая инструкция
  • RUN — выполняется при сборке, CMD/ENTRYPOINT — при запуске
  • COPY – предпочтительный вариант (95% случаев). ADD – если нужна автораспаковка tar-архивов или скачивание по URL.
  • EXPOSE не открывает порт, только документирует его
  • Используйте WORKDIR вместо RUN cd ...

🟡 Middle Level

Классификация инструкций

1. Инструкции определения среды

FROM — задаёт базовый образ. С этого начинается любой Dockerfile. Best Practice: указывайте конкретную версию (openjdk:17-slim), а не latest.

ENV — устанавливает переменные окружения. Доступны и при сборке, и в запущенном контейнере.

ENV JAVA_OPTS="-Xmx512m"
ENV APP_ENV=production

ARG — определяет переменные, доступные только в процессе сборки.

ARG APP_VERSION=1.0.0
RUN echo "Building version $APP_VERSION"

WORKDIR — устанавливает рабочую директорию. Все последующие команды выполняются относительно неё.

WORKDIR /app    # лучше, чем RUN cd /app

2. Инструкции работы с файлами

COPY — копирует файлы с хоста в образ.

COPY pom.xml /app/
COPY src/ /app/src/

ADD — расширенная версия COPY. Умеет распаковывать архивы (.tar.gz) и скачивать файлы по URL.

ADD app.tar.gz /app/  # автоматически распакует

3. Инструкции выполнения команд

RUN — выполняет команду при сборке и фиксирует результат в новом слое.

# Объединяйте команды для уменьшения слоёв
RUN apt-get update && \
    apt-get install -y git curl && \
    rm -rf /var/lib/apt/lists/*
// Удаление в том же RUN критично: если удалить в отдельном RUN,
// файлы останутся в нижнем слое и будут в образе.

CMD — задаёт команду по умолчанию при старте контейнера. Легко переопределить.

CMD ["--server.port=8080"]

ENTRYPOINT — определяет основную команду запуска. Сложнее переопределить.

ENTRYPOINT ["java", "-jar", "/app.jar"]

4. Инструкции настройки доступа

EXPOSE — документирует порт. Не публикует его реально (для этого нужен -p при запуске).

USER — указывает пользователя для запуска.

RUN useradd -r appuser
USER appuser

VOLUME — создаёт точку монтирования для постоянных данных.

VOLUME ["/data"]

Типичные ошибки

Ошибка Последствие Как избежать
RUN apt-get update в отдельном слое Кэш устаревает, пакеты не находятся Объединяйте update && install в один RUN
Использование shell form CMD java -jar Сигналы не доходят до приложения Используйте exec form ["java", "-jar"]
Передача секретов через ARG Пароли видны в docker history Используйте BuildKit secrets (--mount=type=secret)
Удаление файлов в отдельном RUN Файлы остаются в нижних слоях Удаляйте в том же RUN, где создали
Несколько CMD/ENTRYPOINT Учитывается только последняя Одна CMD, одна ENTRYPOINT на файл

Сравнение CMD vs ENTRYPOINT

Инструкция Можно переопределить? Основная цель
ENTRYPOINT С трудом (--entrypoint) Фиксированная команда
CMD Очень легко Параметры по умолчанию

Best Practices

  1. Объединяйте RUN команды через && для уменьшения слоёв
  2. Очищайте кэш пакетов в том же RUN слое
  3. Используйте WORKDIR вместо цепочек RUN cd
  4. Всегда используйте Multi-stage build для разделения сборки и runtime

Что запомнить

  • Каждая RUN, COPY, ADD создаёт новый слой
  • Порядок инструкций влияет на кэширование
  • Используйте Exec form ["cmd", "arg"] вместо Shell form
  • Очищайте кэш в том же слое, где установка
  • ENTRYPOINT + CMD вместе — лучшая практика

🔴 Senior Level

Архитектура инструкций и их влияние на образ

Понимание нюансов инструкций критично для создания безопасных, компактных и быстрых в сборке образов.

Глубокий анализ: слоистая модель

Каждая инструкция RUN, COPY, ADD создаёт новый слой (layer). Слои — read-only файловые системы, объединённые через UnionFS (Overlay2).

Критическое последствие: удаление файла в новом слое не удаляет его из образа — создаётся лишь «whiteout» запись. Размер файла в образе = сумма всех слоёв, в которых он присутствует.

# ПЛОХО: файл остаётся в нижнем слое
RUN apt-get update && apt-get install -y package
RUN rm -rf /var/lib/apt/lists/*

# ХОРОШО: один слой, кэш очищен сразу
RUN apt-get update && \
    apt-get install -y package && \
    rm -rf /var/lib/apt/lists/*

Trade-offs

Решение Плюс Минус
Shell form Удобство (pipes, переменные) PID 1 проблема, сигналы не доходят
Exec form Правильная обработка сигналов Нет shell-функциональности
ARG для конфигурации Просто Видно в docker history, не runtime
ENV для конфигурации Доступно runtime Видно в docker inspect
ADD для URL Не нужен RUN curl/wget Непредсказуемый кэш, нет retry
RUN curl/wget Контроль, retry, checksum Дополнительный слой

ARG vs ENV: тонкости

Характеристика ARG ENV
Доступна при сборке Да Да
Доступна в контейнере Нет Да
Виден в docker inspect Нет Да
Виден в docker history Да (значение!) Да

Security warning: значения ARG видны в docker history. Не передавайте секреты через ARG!

# ПЛОХО: пароль виден в docker history
ARG DB_PASSWORD=secret123

# ХОРОШО: BuildKit secrets
RUN --mount=type=secret,id=db_pass cat /run/secrets/db_pass

Shell form vs Exec form: критический нюанс

Exec form (рекомендуемая):

ENTRYPOINT ["java", "-jar", "/app.jar"]

Запускается напрямую как процесс с PID 1. Правильно обрабатывает сигналы (SIGTERM, SIGKILL). Критично для graceful shutdown в Kubernetes.

Shell form:

ENTRYPOINT java -jar /app.jar

Запускается как подпроцесс /bin/sh -c. Сигналы ОС приходят в оболочку sh, а не в приложение. Приложение может быть «убито» жёстко без завершения транзакций.

Проблема PID 1: в Linux процесс с PID 1 имеет особое поведение — он не получает SIGTERM по умолчанию. Решение: exec form, tini, или docker run --init.

Edge Cases

  • ONBUILD в multi-stage: ONBUILD инструкции выполняются при использовании образа как базового. В multi-stage это может привести к неожиданным побочным эффектам.
  • Glob patterns в COPY: COPY target/*.jar — если файлов нет, сборка падает. Если файлов несколько, копируются все в указанную директорию.
  • Символьные ссылки: COPY следует за symlink’ами на хосте. Это может привести к включению неожиданных файлов.
  • Временные метки: COPY сохраняет mtime файлов. Это влияет на детерминизм сборки. BuildKit --metadata-file помогает отслеживать.
  • ENV и escaping: ENV FOO=bar\ baz — пробел в значении. ENV FOO="bar baz" — кавычки включаются в значение.

HEALTHCHECK: production-обязанность

HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=60s \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

Без HEALTHCHECK оркестратор не знает, живо ли приложение. Контейнер может быть Running, но приложение внутри — мёртвым (deadlock, out of memory).

BuildKit: продвинутые возможности

# Синтаксис BuildKit
# syntax=docker/dockerfile:1

# Секреты
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm install

# SSH forwarding (для private git repos)
RUN --mount=type=ssh git clone git@github.com:org/private-repo.git

# Cache mount (для package managers)
RUN --mount=type=cache,target=/root/.m2 mvn package

# TMPFS mount
RUN --mount=type=tmpfs,target=/tmp make

ONBUILD: инструкция для родительских образов

# В базовом образе
ONBUILD COPY . /app
ONBUILD RUN mvn package

Используется для создания родительских образов, автоматизирующих шаги для потомков. Опасно в multi-stage: ONBUILD не выполняется для subsequent stages.

LABEL: метаданные для CI/CD

LABEL maintainer="team@example.com"
LABEL version="1.0.0"
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.revision="abc123"

Полезно для отслеживания образов в registry, автоматизации, compliance.

Performance

Оптимизация Влияние
Разделение pom.xml и src Кэш hit rate > 90%
Объединение RUN команд На 20-40% меньше слоёв
Alpine вместо full На 60-80% меньше размер
Multi-stage build На 50-70% меньше финальный образ
BuildKit cache mount На 50% быстрее повторные сборки

Резюме

  • Каждая команда RUN, COPY, ADD создаёт новый слой — удаляйте временные файлы в том же слое.
  • Используйте Exec form для CMD и ENTRYPOINT — критично для обработки сигналов.
  • ARG виден в docker history — не передавайте секреты.
  • HEALTHCHECK — обязательный элемент production-образа.
  • BuildKit (--mount=type=secret/cache/ssh) — современный стандарт сборки.
  • Оптимизация слоёв = экономия места в registry + ускорение деплоя.

🎯 Шпаргалка для интервью

Обязательно знать:

  • RUN выполняется при сборке, CMD/ENTRYPOINT — при запуске контейнера
  • Каждая RUN/COPY/ADD создаёт новый слой; удаление в отдельном RUN не удаляет файл из образа
  • Exec form ["cmd", "arg"] критичен для signal handling (graceful shutdown)
  • ARG виден в docker history — не передавайте секреты; используйте BuildKit secrets
  • ENV доступна в runtime, ARG — только при сборке
  • HEALTHCHECK обязателен для production-образов
  • BuildKit: --mount=type=secret/cache/ssh — современный стандарт сборки

Частые уточняющие вопросы:

  • «Почему shell form плоха?» — Запускается через /bin/sh -c, сигналы ОС не доходят до приложения (PID 1 проблема)
  • «Зачем объединять RUN команды через &&?» — Каждая инструкция = слой; объединение уменьшает число слоёв
  • «Что делает ONBUILD?» — Инструкции выполняются при использовании образа как базового (для родительских образов)
  • «EXPOSE открывает порт?» — Нет, только документирует; реальный маппинг через -p при запуске

Красные флаги (НЕ говорить):

  • «EXPOSE делает порт доступным снаружи» (только документирует, нужен -p)
  • «Передаю пароли через ARG» (видно в docker history)
  • «Удаляю файлы в отдельном RUN слое» (файлы остаются в нижнем слое)
  • «Использую shell form для ENTRYPOINT» (проблема PID 1, сигналы теряются)

Связанные темы:

  • [[Что такое Dockerfile]] — основы Dockerfile
  • [[В чём разница между CMD и ENTRYPOINT]] — детально о запуске
  • [[Что такое multi-stage build]] — оптимизация образа