Урок 4 из 8 · 20 мин · обновлено

Dockerfile: собираем свой образ

Как описать свой образ в Dockerfile: инструкции FROM, WORKDIR, COPY, RUN, EXPOSE и CMD, сборка командой docker build, теги версий, кэш слоёв и файл .dockerignore.

Свой образ

Готовые образы закрывают многое, но рано или поздно нужен свой: ваш сайт или программа вместе с окружением. Образ описывают в файле Dockerfile — это рецепт: что взять за основу, что скопировать, что установить и что запускать.

Первый Dockerfile: сайт на nginx

Создайте папку my-site и положите в неё два файла. Простую страницу index.html:

HTML
<!DOCTYPE html>
<html lang="ru">
<head><meta charset="UTF-8"><title>Мой сайт</title></head>
<body><h1>Привет из контейнера!</h1></body>
</html>

И файл Dockerfile — без расширения:

Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html

Всего две строки:

  • FROM nginx:alpine — взять за основу образ nginx;
  • COPY index.html … — скопировать нашу страницу туда, откуда nginx отдаёт сайт.

Что запускать, указывать не нужно: базовый образ nginx уже знает, как стартовать веб-сервер.

Сборка и запуск

Откройте терминал в папке my-site и соберите образ:

Терминал
docker build -t my-site:1.0 .
  • -t my-site:1.0 — имя и тег нового образа;
  • . — контекст сборки: текущая папка, где лежат Dockerfile и файлы.

Запустите контейнер из своего образа:

Терминал
docker run -d --name my-site -p 8080:80 my-site:1.0

Откройте http://localhost:8080 — там ваша страница «Привет из контейнера!». Этот образ можно отправить в реестр и запустить на любом компьютере с Docker.

Изменили страницу — соберите новую версию с другим тегом my-site:1.1. Контейнер сам не обновится: его нужно остановить, удалить и запустить заново из новой версии.

Главные инструкции

Инструкция Что делает
FROM базовый образ, с которого начинаем
WORKDIR рабочая папка внутри образа для следующих инструкций
COPY скопировать файлы из папки сборки в образ
RUN выполнить команду при сборке: установить пакеты, создать папки
EXPOSE отметить, какой порт слушает программа — подсказка, а не открытие порта
CMD что запускать при старте контейнера

Типичный Dockerfile для программы на Python выглядит так:

Dockerfile
FROM python:3.12-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

RUN выполнился один раз, при сборке, и зависимости остались в образе. CMD выполняется каждый раз, когда по образу запускают контейнер.

Кэш слоёв: порядок важен

Каждая инструкция создаёт слой. При повторной сборке Docker проверяет: если инструкция и её файлы не изменились, слой берётся из кэша — мгновенно. Но если слой изменился, пересобираются он и все слои ниже.

Поэтому в примере выше сначала копируется только requirements.txt и ставятся зависимости, а весь код — потом. Код меняется постоянно, зависимости — редко. При правке кода Docker возьмёт слой с установленными зависимостями из кэша, и сборка займёт секунды, а не минуты.

Правило: редко меняющиеся шаги — выше, часто меняющиеся — ниже.

.dockerignore

Команда COPY . . копирует всю папку. В неё может случайно попасть лишнее: папка .git, временные файлы и, хуже всего, файлы с паролями. Файл .dockerignore рядом с Dockerfile исключает их из сборки — так же, как .gitignore в Git:

Текст
.git
.env
*.log
__pycache__

Попробуйте сами. Соберите образ сайта из урока с тегом 1.0 и запустите его. Затем измените текст на странице, соберите версию 1.1 и запустите её на порту 8081 рядом с первой. Откройте обе версии в браузере.

Решение
Терминал
docker build -t my-site:1.0 .
docker run -d --name site-v1 -p 8080:80 my-site:1.0

# поменяли текст в index.html
docker build -t my-site:1.1 .
docker run -d --name site-v2 -p 8081:80 my-site:1.1

docker images my-site

На localhost:8080 — старый текст, на localhost:8081 — новый. Обе версии живут рядом: образы неизменяемы, и каждый тег — отдельный снимок.

При второй сборке обратите внимание на вывод: шаг FROM взят из кэша — строка с CACHED, а COPY выполнен заново, потому что файл изменился.

Уберите за собой:

Терминал
docker stop site-v1 site-v2
docker rm site-v1 site-v2

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Чем RUN отличается от CMD?

  2. Что означает точка в конце docker build -t my-site:1.0 .?

  3. Почему COPY requirements.txt и установку зависимостей ставят выше, чем COPY всего кода?

  4. Зачем нужен файл .dockerignore?