Які основні інструкції використовуються в Dockerfile
Інструкції в Dockerfile є командами предметно-орієнтованої мови (DSL), які послідовно виконуються збирачем Docker для створення шарів та метаданих образу.
🟢 Junior Level
30-секундна відповідь
Інструкції в Dockerfile є командами предметно-орієнтованої мови (DSL), які послідовно виконуються збирачем Docker для створення шарів та метаданих образу.
Усі основні інструкції поділяються на 4 логічні групи:
- Середовище та контекст:
FROM(базовий образ),WORKDIR(робоча папка),ENV(змінні рантайму),ARG(змінні збірки). - Файлові операції:
COPY(копіювання з хоста),ADD(копіювання з авторозпакуванням tar). - Виконання команд:
RUN(виконується під час збірки образу),CMDтаENTRYPOINT(виконуються в момент старту контейнера). - Конфігурація та безпека:
USER(непривілейований користувач),EXPOSE(документація портів),VOLUME(точка монтування тому),HEALTHCHECK(перевірка життєздатності).
Наочний приклад для Java 21
# 1. Задання базового образу
FROM eclipse-temurin:21-jre-jammy
# 2. Робоча директорія (створюється автоматично, якщо її немає)
WORKDIR /app
# 3. Змінна оточення для JVM
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 4. Копіювання JAR-файлу
COPY target/service.jar app.jar
# 5. Створення та перемикання на непривілейованого користувача
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
# 6. Документування порту застосунку
EXPOSE 8080
# 7. Фіксована команда запуску
ENTRYPOINT ["java", "-jar", "app.jar"]
🟡 Middle Level
Детальний розбір ключових інструкцій
1. RUN vs CMD vs ENTRYPOINT (Головна плутанина початківців)
RUN: виконується один раз на етапі збірки образу (docker build). Результат виконання команди фіксується в новому шарі файлової системи (встановлення пакетівapt-get, компіляція, створення користувачів).ENTRYPOINT: задає головний виконуваний бінарник, який стартує при запуску контейнера (docker run). Перевизначити його можна лише явним прапорцем--entrypoint.CMD: задає аргументи за замовчуванням дляENTRYPOINT. Легко перевизначається при передачі аргументів у кінці командиdocker run.
2. COPY vs ADD
COPY: просте, прозоре та безпечне копіювання файлів і папок із хоста в образ. Рекомендується в 95% випадків.ADD: розширена версія:- Автоматично розпаковує локальні tar-архіви (
.tar,.tar.gz,.tar.bz2) у цільову директорію. - Вміє завантажувати файли за URL (проте в Production для URL заведено використовувати
RUN curl -fSL ..., оскількиADDне вміє очищати кеш завантаженого архіву).
- Автоматично розпаковує локальні tar-архіви (
3. ARG vs ENV
| Параметр | ARG (Build Argument) | ENV (Environment Variable) |
| :— | :— | :— |
| Коли доступна | Лише під час збірки (docker build) | І при збірці, і всередині запущеного контейнера |
| Перевизначення | Прапорець --build-arg KEY=value | Прапорець -e KEY=value при docker run |
| Безпека | Видна в docker history (не для паролів!) | Видна в docker inspect контейнера |
4. EXPOSE та VOLUME
EXPOSE 8080: є виключно документацією для людини та оркестратора. Інструкція НЕ відкриває порт назовні. Щоб порт став доступним із хоста, його потрібно явно прокинути:docker run -p 8080:8080.VOLUME ["/data"]: оголошує директорію точкою монтування зовнішнього сховища. Дані, що записуються в цю директорію, зберігаються поза шарами OverlayFS.
🔴 Senior Level
Золоте правило зменшення шарів: Chaining у RUN
Оскільки кожен крок RUN створює незмінний шар, очищення тимчасових файлів має відбуватися в межах тієї ж самої сесії bash:
# ❌ КАТАСТРОФА: Кеш apt залишився в нижньому шарі, образ важить на 150 МБ більше!
RUN apt-get update
RUN apt-get install -y curl jq
RUN rm -rf /var/lib/apt/lists/*
# ✅ ПРАВИЛЬНО: Один шар, чиста файлова система
RUN apt-get update && \
apt-get install -y --no-install-recommends curl jq && \
rm -rf /var/lib/apt/lists/*
Exec Form vs Shell Form і проблема сигналів PID 1
Будь-яка інструкція запуску (RUN, CMD, ENTRYPOINT) може бути записана у двох формах:
- Shell Form (Рядок без дужок):
ENTRYPOINT java -jar app.jarDocker неявно загортає команду в
/bin/sh -c "java -jar app.jar". Оболонкаshстає процесом із PID 1, а JVM — її дочірнім процесом. СигналSIGTERMвід Kubernetes не доходить до Java: застосунок не встигає закрити з’єднання з БД та скинути кеші, завершуючись аварійно заSIGKILL. - Exec Form (JSON-масив рядків):
ENTRYPOINT ["java", "-jar", "app.jar"]Команда викликається ядром напряму через системний виклик
execve(). Процес JVM стає PID 1, коректно перехоплюючиSIGTERMта запускаючи Spring Boot Shutdown Hooks.
Тонкощі роботи з VOLUME в Dockerfile
Senior-розробник зобов’язаний пам’ятати про пастку порядку інструкцій із VOLUME:
- Щойно в Dockerfile оголошено інструкцію
VOLUME ["/app/data"], Docker переводить цю папку в режим зовнішнього анонімного тому для всіх наступних інструкцій збірки. - Якщо на наступному кроці виконати
RUN chown -R appuser:appgroup /app/dataабо покласти туди початкові файли, ці зміни не збережуться в підсумковому образі, оскільки застосовуються до тимчасового анонімного тому, що видаляється після збірки кроку! - Правило: створення директорії, призначення прав
chownта ініціалізація дефолтних файлів мають виконуватися СТРОГО ДО оголошення директивиVOLUME.
Інструкція HEALTHCHECK для оркестраторів
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
- Дозволяє Docker Engine відстежувати реальний статус сервісу: статус контейнера змінюється з
healthyнаunhealthy. - Опція
--start-period=60sдає Spring Boot застосунку час на «холодний старт» та прогрів JIT без хибних перезапусків.
4 Tricky Questions
1. Яка прихована небезпека виникає, якщо інструкція RUN chown -R appuser /app/data розміщена ПІСЛЯ інструкції VOLUME ["/app/data"]?
Відповідь:
Щойно Docker зустрічає інструкцію VOLUME ["/app/data"], він позначає цей шлях як том.
На кожному наступному кроці збірки (включаючи RUN chown ...) Docker монтує для збірки цього кроку новий тимчасовий анонімний том, виконує команду chown, фіксує шар і… викидає тимчасовий том разом із усіма зміненими правами доступу!
У результаті в підсумковому образі права доступу на папку /app/data залишаться належати користувачу root. При запуску контейнера від USER appuser застосунок отримає помилку Permission Denied при спробі запису в /app/data. Будь-яке налаштування прав і файлів директорії зобов’язане проводитися до оголошення VOLUME.
2. У чому різниця області видимості (Scope) у змінної ARG, оголошеної ДО першої інструкції FROM, та ARG, оголошеної ПІСЛЯ FROM?
Відповідь:
Інструкція ARG, оголошена до першого рядка FROM, перебуває в глобальній області видимості збірки.
Вона призначена виключно для параметризації самих базових образів (наприклад, ARG JAVA_VER=21 $\to$ FROM eclipse-temurin:${JAVA_VER}):
- Після виконання інструкції
FROMпочинається конкретний етап збірки (Build Stage), і глобальнийARGвиходить з області видимості. - Будь-які наступні інструкції
RUN echo $JAVA_VERвсередині етапу виведуть порожній рядок! - Щоб використовувати цю змінну всередині кроку збірки, її необхідно повторно оголосити всередині етапу без значення:
ARG JAVA_VER=21 FROM eclipse-temurin:${JAVA_VER} ARG JAVA_VER # Повторне оголошення робить змінну доступною всередині етапу! RUN echo "Building with Java $JAVA_VER"
3. Як взаємодіють ENTRYPOINT та CMD, якщо обидві інструкції оголошені в Exec Form? Покажіть на прикладі запуску Spring Boot.
Відповідь:
Якщо обидві інструкції оголошені в Exec Form, Docker об’єднує їхні масиви: аргументи з CMD передаються як завершальні параметри в команду ENTRYPOINT:
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--server.port=8080", "--spring.profiles.active=prod"]
При звичайному запуску docker run my-app виконається команда:
java -jar app.jar --server.port=8080 --spring.profiles.active=prod.
Однак якщо користувач запустить контейнер із власними аргументами:
docker run my-app --server.port=9090,
то переданий аргумент повністю замістить масив CMD, і виконається:
java -jar app.jar --server.port=9090. Це стандартний патерн створення гнучких мікросервісних образів.
4. Чому для завантаження віддалених архівів через HTTP інструкція RUN curl -fSL ... && tar -xz && rm краща за вбудовану можливість ADD http://...?
Відповідь:
- Інструкція
ADD http://example.com/archive.tar.gz /tmp/не вміє автоматично розпаковувати віддалені архіви, завантажені через HTTP (авторозпакування працює лише для локальних tar-файлів із Build Context). - Завантажений архів зберігається в незмінному шарі Docker-образу. Навіть якщо на наступному кроці викликати
RUN rm /tmp/archive.tar.gz, архів залишиться всередині шару образу назавжди, збільшуючи його розмір. - Інструкція
ADDне підтримує перевірку контрольних сум sha256, автентифікацію та повторні спроби при збої мережі. Зв’язкаRUN curl ... | tar -xzв одному рядку завантажує, розпаковує та видаляє потік байтів на льоту, не створюючи сміттєвих проміжних шарів.
🎯 Шпаргалка для інтерв’ю
Зведена таблиця інструкцій Dockerfile
| Інструкція | Стадія | Призначення | Особливості |
| :— | :— | :— | :— |
| FROM | Build | Базовий образ | Перша обов’язкова інструкція |
| WORKDIR| Build/Run | Робоча папка | Створює шлях автоматично, замінює cd |
| RUN | Build | Збірка та встановлення | Створює новий шар; об’єднувати через && \ |
| COPY | Build | Копіювання файлів | Безпечно та прозоро; використовувати --chown |
| ADD | Build | Копіювання + розпакування | Авторозпакування .tar.gz; уникати для URL |
| ARG | Build | Змінна збірки | Не зберігається в контейнері; видна в історії |
| ENV | Build/Run | Змінна середовища | Доступна застосунку в runtime |
| USER | Run | Користувач процесу | Захист від root: USER 1000:1000 |
| EXPOSE | Мета | Документація порту | Не публікує порт сам по собі |
| ENTRYPOINT| Run | Основна команда | Exec-форма ["cmd"] для коректного PID 1 |
| CMD | Run | Аргументи за замовчуванням | Легко перевизначається при docker run |
Червоні прапорці на співбесіді (Чого говорити не можна)
- ❌ «EXPOSE відкриває доступ до порту контейнера з інтернету» — порт публікується лише прапорцем
-p 8080:8080. - ❌ «CMD та ENTRYPOINT — це одне й те саме» — ENTRYPOINT фіксує виконуваний файл, CMD задає параметри за замовчуванням.
- ❌ «Паролі до БД потрібно передавати через ARG, щоб їх не було видно в контейнері» — вони залишаються в маніфесті образу
docker history. - ❌ «Для переходу в папку я пишу RUN cd /app» — кожен RUN виконується в новому підоболонковому процесі, потрібно використовувати
WORKDIR /app.