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

Что такое Dockerfile?

Это декларативный файл: вы описываете, каким должен быть образ, а Docker сам решает, как его собрать. Это как рецепт: вы пишете шаги, а Docker собирает из них готовый образ, кот...

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

🟢 Junior Level

Простое объяснение

Dockerfile — это текстовый файл с инструкциями для создания Docker-образа.

Это декларативный файл: вы описываете, каким должен быть образ, а Docker сам решает, как его собрать. Это как рецепт: вы пишете шаги, а Docker собирает из них готовый образ, который можно запустить как контейнер.

Аналогия

Dockerfile — это кулинарный рецепт. FROM — это основа (например, тесто), COPY — добавление ингредиентов, RUN — приготовление, ENTRYPOINT — способ подачи блюда. Из одного рецепта можно приготовить сколько угодно одинаковых блюд (контейнеров).

Пример

# 1. Базовый образ
FROM openjdk:17-jdk-slim

# 2. Рабочая директория
WORKDIR /app

# 3. Копирование файла
COPY myapp.jar app.jar

# 4. Порт приложения
EXPOSE 8080

# 5. Команда запуска
ENTRYPOINT ["java", "-jar", "app.jar"]
# Собрать образ из Dockerfile
docker build -t myapp .

# Запустить контейнер
docker run -p 8080:8080 myapp

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

Инструкция Что делает
FROM Указывает базовый образ (например, openjdk:17)
WORKDIR Задаёт рабочую директорию
COPY Копирует файлы в образ
RUN Выполняет команду при сборке
EXPOSE Указывает порт приложения
ENTRYPOINT Команда запуска приложения

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

  • Dockerfile — это рецепт для создания Docker-образа
  • Каждая инструкция создаёт новый слой в образе
  • FROM — всегда первая инструкция
  • Используйте конкретные версии образов, а не latest
  • docker build собирает образ, docker run запускает контейнер

Когда НЕ нужен Dockerfile

Если используете platform-as-a-service (Heroku, Railway, Render), Dockerfile может не понадобиться – платформа сама собирает образ из кода.


🟡 Middle Level

Как работает сборка образа

Когда вы запускаете docker build .:

  1. Docker-клиент передаёт содержимое текущей папки (Build Context) Docker-демону.
  2. Демон пошагово выполняет инструкции из Dockerfile.
  3. Каждая инструкция создаёт новый слой (layer) в образе.
  4. Слои кэшируются — если инструкция не изменилась, Docker использует кэш.

Build Context и .dockerignore

Build Context — это всё содержимое директории, переданное Docker. Чтобы не передавать лишние файлы (.git, target/, логи), используйте .dockerignore:

.git
target/
*.log
.idea/

Понятие слоёв (Layers) и кэширование

Docker использует слоистую файловую систему (UnionFS/Overlay2):

  • Каждая команда (RUN, COPY, ADD) создаёт новый слой
  • Слои кэшируются — если инструкция и файлы не изменились, Docker берёт готовый слой из кэша

Правило оптимизации: Располагайте редко меняющиеся инструкции в начале, часто меняющиеся — в конце.

# ПЛОХО: при каждом изменении кода кэш зависимостей сбрасывается
COPY src /app/src
COPY pom.xml /app
RUN mvn -f /app/pom.xml clean package

# ХОРОШО: зависимости кэшируются отдельно
COPY pom.xml /app
RUN mvn -f /app/pom.xml dependency:go-offline
// Docker кэширует слой, если инструкция и ВСЕ предыдущие слои не изменились.
// Изменился pom.xml -- invalidate всех последующих слоёв.
COPY src /app/src
RUN mvn -f /app/pom.xml clean package

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

Ошибка Последствие Как избежать
FROM openjdk:latest Недетерминированные сборки FROM openjdk:17-jdk-slim
Нет .dockerignore Медленная сборка, большие образы Создайте .dockerignore
Все команды в одном RUN Невозможно использовать кэш частично Разделяйте логические шаги
Запуск приложения от root Риск безопасности USER appuser
Один огромный слой Нет кэширования, медленный rebuild Разделяйте зависимости и код

Основные принципы хорошего Dockerfile

  1. Единственная ответственность — один контейнер, один процесс.
  2. Минимизация размера — используйте лёгкие базовые образы (alpine, slim, distroless).

Alpine – минимальный Linux-дистрибутив (~5 МБ), часто используется как база для образов.

  1. Безопасность — не запускайте приложение от root. Используйте USER.
  2. Конкретные версии — всегда указывайте точные версии базовых образов.

Multi-stage build

Позволяет компилировать код в одном временном образе, а в итоговый копировать только артефакт:

# Stage 1: Build
FROM maven:3.8-openjdk-17 AS build
COPY src /app/src
COPY pom.xml /app
RUN mvn -f /app/pom.xml clean package -DskipTests

# Stage 2: Runtime
FROM openjdk:17-jdk-slim
COPY --from=build /app/target/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

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

  • Каждая инструкция = новый слой
  • Кэширование — ключ к быстрой сборке
  • Порядок инструкций важен для производительности
  • Используйте .dockerignore для исключения лишних файлов
  • Multi-stage build — стандарт для продакшен-образов

🔴 Senior Level

Dockerfile как Infrastructure as Code

Dockerfile — это не просто скрипт сборки, это фундамент Infrastructure as Code (IaC) для уровня приложений. Он определяет воспроизводимость, безопасность и эффективность доставки ПО.

Глубокий анализ процесса сборки

Build Context и его влияние на CI/CD

docker build . → tar-архив всей директории → отправка Docker daemon

Проблемы: большой контекст = медленная отправка (особенно в remote daemon). .dockerignore работает как .gitignore, но для Docker. В CI/CD контекст может включать артефакты предыдущих сборок.

Best Practice:

# Исключаем всё, кроме нужного
**
!src/
!pom.xml
!Dockerfile

Механизм кэширования: глубокое понимание

Docker вычисляет хэш каждого слоя на основе: самой инструкции, хэшей предыдущих слоёв, содержимого файлов (для COPY и ADD).

Кэш invalidation происходит когда: изменилась инструкция, изменились копируемые файлы, изменился хэш родительского слоя.

Паттерн “Dependency Layer” для Java:

FROM maven:3.8-openjdk-17 AS build
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B   # редко меняется → кэшируется
COPY src ./src
RUN mvn package -DskipTests        # часто меняется

Это даёт 90%+ hit rate кэша при сборках без изменения зависимостей.

Trade-offs

Решение Плюс Минус
Alpine-образы Минимальный размер (~5MB base) musl libc ≠ glibc, проблемы с native libraries
Slim-образы glibc совместимость, маленький размер Больше alpine
Distroless Минимальная поверхность атаки Нет shell для отладки, нужны ephemeral debug containers
Много слоёв Лучшее кэширование Больше мета-данных, медленнее pull
Мало слоёв Быстрый pull Хуже кэширование

Edge Cases

  • Alpine + native libraries: Alpine использует musl libc. JNI-библиотеки (Netty epoll, PostgreSQL native) могут требовать glibc. Решение: используйте debian-slim или собирайте под musl.
  • Таймауты при сборке: RUN apt-get update может зависнуть из-за сетевых проблем. Решение: используйте зеркала, retry-логику.
  • Non-deterministic builds: apt-get update без фиксации версий пакетов даёт разные результаты. Решение: apt-get install -y package=1.2.3-1.
  • Cross-platform builds: Сборка amd64 образа на ARM (Apple Silicon). Решение: docker buildx с QEMU эмуляцией или remote builders.

Безопасность Dockerfile

# Production-ready Dockerfile для Spring Boot
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B

FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --from=build /build/target/*.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseG1GC", "-jar", "app.jar"]

Критические правила:

  • Не используйте latest
  • Не запускайте от root
  • Read-only filesystem где возможно
  • Минимизируйте поверхность атаки
  • Используйте HEALTHCHECK

Влияние на CI/CD pipeline

developer → git push → CI запускает docker build →
  cache hit? → быстро (секунды) : медленно (минуты) →
  docker push → CD деплоит

Оптимизация CI: registry cache (pull предыдущего образа), BuildKit cache, layer sharing между образами.

BuildKit (Docker 23+): параллельное выполнение независимых шагов, секрет-менеджмент (--mount=type=secret), SSH forwarding (--mount=type=ssh), cache mounts (--mount=type=cache).

Performance

Базовый образ Размер Время pull Время start
openjdk:17 ~500 МБ ~30s ~5s
openjdk:17-slim ~300 МБ ~15s ~5s
eclipse-temurin:17-jre-alpine ~100 МБ ~5s ~4s
distroless/java17 ~80 МБ ~4s ~4s

Monitoring

  • docker history <image> — анализ слоёв и их размеров
  • docker build --progress=plain — детальный вывод сборки
  • dive <image> — интерактивный анализатор слоёв
  • BuildKit --progress=trace — tracing каждого шага

Production Story

Команда из 50 разработчиков столкнулась с тем, что CI-сборка занимала 12 минут. Анализ показал: каждый раз загружались все Maven-зависимости заново. Внедрение multi-stage build с раздельным кэшированием pom.xml сократило время до 2 минут (83% improvement). Дополнительно: переход на slim-образ уменьшил размер с 650MB до 280MB, что ускорило деплой в 2.3 раза и сэкономило 40% storage в registry.

Резюме

  • Dockerfile — рецепт создания иммутабельного артефакта. Понимание кэширования слоёв — ключ к быстрым CI/CD.
  • Всегда стремитесь к минимизации слоёв и Multi-stage сборке.
  • Dockerfile должен быть детерминированным (конкретные версии, не latest).
  • Безопасность: non-root пользователь, минимальный базовый образ, health checks.
  • На масштабе оптимизация Dockerfile экономит сотни часов CI/CD и гигабайты storage.

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

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

  • Dockerfile — декларативный рецепт создания иммутабельного Docker-образа
  • Каждая инструкция (RUN, COPY, ADD) создаёт новый read-only слой
  • Кэширование слоёв — ключ к быстрой сборке: порядок инструкций критичен
  • Multi-stage build — стандарт для production: сборка в одном образе, runtime в другом
  • Exec form ["cmd", "arg"] обязателен для CMD/ENTRYPOINT (signal handling)
  • Безопасность: non-root пользователь, конкретные теги, минимальный базовый образ
  • BuildKit: секреты (--mount=type=secret), SSH forwarding, cache mounts

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

  • «Почему важен порядок инструкций?» — Изменение инструкции invalidates все последующие слои кэша
  • «Что такое .dockerignore?» — Исключает файлы из build context (как .gitignore для Docker)
  • «Почему Alpine может быть проблемой?» — musl libc ≠ glibc; JNI-библиотеки могут не работать
  • «Чем COPY отличается от ADD?» — ADD умеет распаковывать архивы и скачивать по URL, но COPY предпочтительнее

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

  • «Использую latest тег для удобства» (недетерминированные сборки)
  • «Секреты передаю через ARG» (видны в docker history)
  • «Запускаю приложение от root в контейнере» (риск безопасности)
  • «ADD лучше чем COPY» (COPY — best practice в 95% случаев)

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

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