Для того, чтобы создать свой собственный Docker образ необходимо описать все инструкции, которые должны выполняться внутри образа. Для этого необходимо создать файл с инструкциями, который называется Dockerfile. Имя файла конечно же может быть и другим, но как правило чаще всего используется имя Dockerfile.

Для примера возьмём следующий Dockerfile:

# Можно указать версию, digest или alias.
FROM python:3.11-slim

# LABEL  добавляет произвольные метаданные, например автора, версию и описание.
LABEL maintainer="student@example.com" \
      version="1.0" \
      description="Учебный Dockerfile со всеми основными инструкциями."

# ARG  аргументы доступны только во время сборки (build-time).
# Они не сохраняются внутри контейнера.
ARG APP_VERSION=1.0
ARG DEBIAN_FRONTEND=noninteractive

# ENV  сохраняет переменные в образе, они доступны во время запуска.
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    APP_HOME=/usr/src/app \
    APP_ENV=production

# WORKDIR  задаёт текущую директорию для всех последующих инструкций.
WORKDIR $APP_HOME

# COPY  копирует файлы из контекста сборки (локальной папки) в контейнер.
COPY requirements.txt .
COPY app.py $APP_HOME

# RUN  выполняет команды Linux во время сборки и создаёт новый слой.
# Используется для установки зависимостей и пакетов.
RUN apt-get update && \
    apt-get install -y --no-install-recommends build-essential git curl && \
    pip install --no-cache-dir -r requirements.txt && \
    rm -rf /var/lib/apt/lists/*

# ADD  как COPY, но поддерживает загрузку из URL и распаковку архивов.
ADD . .

# RUN groupadd/useradd  создаёт не-root пользователя для запуска приложения.
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

# USER  задаёт пользователя, от имени которого будут выполняться команды в контейнере.
USER appuser

# VOLUME  создаёт точку монтирования (для хранения данных или логов).
# создаёт анонимный том (volume), если нужносохранять данные после удаления контейнера
VOLUME ["/usr/src/app/data"]

# EXPOSE  документирует, какие порты слушает приложение.
# Это не открывает порт наружу, но помогает понять его назначение.
EXPOSE 8000

# ENTRYPOINT  основная команда, которая выполняется при запуске контейнера.
# CMD  аргументы по умолчанию для ENTRYPOINT (можно переопределить в docker run).
ENTRYPOINT ["python"]
CMD ["app.py"]

# HEALTHCHECK  Docker периодически выполняет команду, чтобы убедиться, что контейнер “жив”.
# Если команда завершается с кодом != 0, контейнер считается unhealthy.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

ИНСТРУКЦИИ DOCKERFILE

Сборка образа

Для того чтобы собрать образ используется команда docker build, для задания имени образа и тэга используется опция -t. Точка в конце команды указывает что Dockerfile и другие файлы, необходимые для создания образа нужно искать в текущей директории.

Если же имя файла отличается от Dockerfile необходимо через опцию -f указать имя файла.

docker build -t myapp:1.0 .
docker image ls

В первой команде я указываю имя образа myapp без NAMESPACE потому что я не собираюсь загружать этот образ в репозиторий, подробнее про NAMESPACE.

После удачной сборки осталось запустить контейнер с этого образа и проверить его доступность. Так как в Dockerfile объявлен порт (EXPOSE 8000) прокидываю его на локальный порт 8000 через -p.

docker run --name myapp -d -p 8000:8000 myapp:1.0
curl http://localhost:8000/

Что делает .dockerignore

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

Дело в том что при запуске docker build -t myapp:1.0 . Docker берёт весь контент в текущей директории, т.е. не важно, что в самом Dockerfile вы копируете в образ всего пару файлов. Да, в самом образе не будет ничего лишнего, но вот во время сборки Docker упаковывает всё содержимое в архив и отправляет Docker-демону, который потом вытаскивает оттуда-то что нужно. В следствии чего тратятся лишние ресурсы (CPU, RAM, IO) и время сборки увеличивается.

.dockerignore — ОПТИМИЗАЦИЯ СБОРКИ

Для примера у меня в директории с Dockerfile есть файл zerofile.bin объёмом 10 GiB и я попробую собрать образ без файла .dockerignore.

docker build -t myapp:1.0 .
=> => transferring context: 10.74GB
[+] Building 88.5s (11/11) FINISHED

Теперь сделаю тоже самое только с использованием .dockerignore, в котором перечислю файлы, которые нужно пропустить. Файл .dockerignore нужно создать в той же директории где и Dockerfile.

📜 .dockerignore

*.bin
docker build -t myapp:1.0 .
 => => transferring context: 4.20kB 
 [+] Building 2.7s (11/11) FINISHED