Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Образи й Dockerfile»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

  • Образ (image) - незмінний шаблон: файлова система з усім потрібним (ОС-шар, PHP, розширення, код, залежності) і метадані - яку команду запускати, які порти слухати. Збирається з Dockerfile, зберігається в реєстрі (Docker Hub, GHCR).
  • Контейнер - запущений екземпляр образу: ізольований процес з власною файловою системою, мережею й обмеженнями ресурсів.

Аналогія: образ - як клас, контейнер - як об'єкт. З одного образу можна запустити скільки завгодно контейнерів.

docker build -t myapp:1.4 .         # зібрати образ
docker run -d --name web myapp:1.4  # запустити контейнер
docker ps                           # запущені контейнери
docker images                       # локальні образи

Шари. Образ складається з шарів тільки для читання - по одному на інструкцію Dockerfile, що змінює файли. Контейнер додає зверху тонкий записуваний шар. Тому:

  • десять контейнерів з одного образу ділять його шари й не займають десятикратно більше місця;
  • зміни, зроблені всередині контейнера, зникають разом із ним. Дані, що мають жити довше (база, завантажені файли), зберігають у томах (volumes).

Практичне правило: контейнер має бути «одноразовим» - його можна знищити й створити заново з того ж образу без втрати даних. Змінити застосунок - це зібрати новий образ, а не зайти в контейнер і поправити файл.

Докладніше в документації: Що таке образ

Кожна інструкція Dockerfile (RUN, COPY, ADD...) створює шар. Під час повторного збирання Docker перевіряє, чи змінилася інструкція та файли, що в неї входять. Якщо ні - бере шар з кешу. Щойно один шар змінився, усі наступні збираються заново.

Погано:

COPY . /var/www
RUN composer install --no-dev

Будь-яка зміна в коді (навіть README) інвалідує COPY ., і composer install виконується щоразу - хвилини на кожне збирання.

Добре: спершу те, що змінюється рідко, потім - часто:

COPY composer.json composer.lock /var/www/
RUN composer install --no-dev --no-scripts --no-autoloader

COPY . /var/www
RUN composer dump-autoload --optimize

Тепер залежності перевстановлюються лише при зміні composer.json/composer.lock, а зміна коду перебудовує тільки останні шари.

Ще правила:

  • .dockerignore - щоб у контекст збирання не потрапляли vendor/, node_modules/, .git/, .env, логи. Інакше COPY . інвалідується через файли, що до застосунку не мають стосунку, а секрети можуть потрапити в образ.
  • Встановлення пакетів ОС - на початку, одним RUN, разом з очищенням кешу менеджера пакетів.
  • Кеш-монтування BuildKit зберігає кеш Composer чи npm між збираннями навіть після інвалідації шару:
RUN --mount=type=cache,target=/root/.composer/cache composer install
  • У CI кеш зникає з кожною новою машиною - його експортують у реєстр (--cache-to/--cache-from).

Докладніше в документації: Кеш збирання

Обидві інструкції додають файли в образ, але ADD робить більше, і саме це робить його менш передбачуваним.

COPY - просто копіює файли й каталоги з контексту збирання (або з іншої стадії --from=):

COPY composer.json composer.lock ./
COPY --chown=www-data:www-data . /var/www/html

ADD уміє ще й:

  • завантажувати файли за URL: ADD https://example.com/app.tar.gz /tmp/;
  • автоматично розпаковувати локальні архіви (.tar, .tar.gz, .tar.xz) у каталог призначення;
  • клонувати Git-репозиторії: ADD https://github.com/org/repo.git#v1.2 /src.

Чому за замовчуванням - COPY:

  • передбачуваність: COPY archive.tar.gz /app/ копіює архів як файл. ADD з тим самим рядком розпакує його - поведінка залежить від типу файлу;
  • кеш: для URL Docker мусить завантажити файл, щоб перевірити, чи він змінився;
  • безпека: завантаження з інтернету без перевірки вмісту - ризик ланцюжка постачання.

Коли ADD доречний:

  • розпакувати локальний архів одним кроком (наприклад, бінарник з архіву поруч із Dockerfile);
  • завантажити файл із перевіркою контрольної суми - сучасний ADD підтримує --checksum=sha256:..., тож вміст гарантовано той, що очікувався;
  • Git-репозиторій як джерело без окремого git clone в образі.

Корисні параметри обох інструкцій:

  • --chown=user:group - власник файлів одразу при копіюванні. Окремий RUN chown -R після COPY створює ще один шар з копією всіх файлів - образ подвоюється в розмірі;
  • --chmod=755 - права доступу;
  • --link - копіювання незалежним шаром, який не інвалідується при зміні попередніх шарів (корисно для фінальних стадій).

Що потрапляє в контекст визначає .dockerignore - без нього COPY . . скопіює .git, node_modules, .env і локальні логи.

Правило Docker Best Practices: COPY для локальних файлів; ADD - лише коли потрібні його додаткові можливості, і то свідомо.

Докладніше в документації: Dockerfile: ADD

Образ - це набір шарів, і кожна інструкція Dockerfile, що змінює файлову систему (RUN, COPY, ADD), створює новий шар. Розмір образу - сума шарів.

Історія шарів:

docker image history myapp:latest
IMAGE          CREATED BY                                      SIZE
a1b2c3d4e5f6   COPY . /var/www/html                            48MB
<missing>      RUN composer install --no-dev                   62MB
<missing>      RUN apt-get update && apt-get install ...       310MB
<missing>      FROM php:8.5-fpm                                 ...

Видно, яка інструкція скільки додала. --no-trunc показує повні команди.

Метадані образу:

docker image inspect myapp:latest

Змінні оточення, ENTRYPOINT/CMD, відкриті порти, мітки, архітектура (amd64/arm64), кількість шарів.

Що всередині файлової системи:

docker run --rm -it myapp:latest sh          # зайти й подивитися
docker run --rm myapp:latest du -sh /var/www/html/* | sort -h

Інструмент dive показує вміст кожного шару і файли, які були додані в одному шарі й видалені в наступному (марнування місця).

Типові причини великих образів:

  • кеш менеджерів пакетів: apt-get install без rm -rf /var/lib/apt/lists/* в тому самому RUN;
  • інструменти збирання у фінальному образі: компілятори, git, node_modules для збирання фронтенду - їх прибирає multi-stage build;
  • зайве в контексті: .git, тести, локальні файли - потрібен .dockerignore;
  • chown -R окремою інструкцією - копія всіх файлів у новому шарі;
  • важкий базовий образ - повний Debian замість slim.

Чому docker image ls і реальний розмір відрізняються: спільні шари базового образу зберігаються на диску один раз для всіх образів, що на ньому побудовані. docker system df -v показує, скільки займають шари насправді.

Розмір має значення: час завантаження при деплої й масштабуванні, місце в реєстрі, поверхня атаки (кожен зайвий пакет - потенційна вразливість).

Докладніше в документації: docker image history

Офіційний образ php має кілька варіантів - вони відрізняються тим, як запускається PHP:

Тег Що всередині Для чого
php:8.5-cli лише PHP CLI команди, воркери черг, скрипти
php:8.5-fpm PHP-FPM (FastCGI) за Nginx/Caddy, класичний веб-стек
php:8.5-apache Apache з mod_php простий «все в одному» веб-сервер
php:8.5-zts збірка з потокобезпекою розширення на кшталт parallel, вбудовування

Кожен варіант має основу Debian (за замовчуванням) або Alpine (php:8.5-fpm-alpine) - менший розмір, але інша бібліотека C (musl), що інколи дає несумісності.

Альтернатива для Laravel - образи FrankenPHP (dunglas/frankenphp): сучасний сервер на основі Caddy з вбудованим PHP, HTTPS і режимом воркера для Octane - один процес замість зв'язки Nginx + PHP-FPM.

Встановлення розширень - допоміжні скрипти офіційного образу:

FROM php:8.5-fpm

RUN apt-get update \
    && apt-get install -y --no-install-recommends libpq-dev libzip-dev libicu-dev \
    && docker-php-ext-install pdo_pgsql zip intl opcache \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && rm -rf /var/lib/apt/lists/*
  • docker-php-ext-install - розширення з вихідних кодів PHP (pdo_mysql, intl, zip, gd, opcache...);
  • docker-php-ext-configure - параметри перед збиранням (наприклад, gd --with-jpeg --with-freetype);
  • pecl install + docker-php-ext-enable - розширення з PECL (redis, xdebug, imagick);
  • системні бібліотеки (libpq-dev, libzip-dev) треба встановити самостійно - їх назви різняться в Debian і Alpine.

Простіше - install-php-extensions (проєкт mlocati): сам встановлює потрібні системні залежності для обох дистрибутивів:

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions pdo_pgsql intl zip redis opcache

Конфігурація PHP: готові шаблони php.ini-production / php.ini-development лежать у $PHP_INI_DIR; власні налаштування - окремим файлом у $PHP_INI_DIR/conf.d/.

Пастка: інструменти збирання (*-dev пакети, компілятори) лишаються в образі - їх прибирають у тому самому RUN чи через multi-stage build.

Докладніше в документації: Docker Hub: офіційний образ PHP