Питання 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.

Продуктивність

Оптимізація Вплив
Розділення 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]] — оптимізація образу