📖 Теория: Dockerfile, nginx и БД в Docker

⚡ Кратко: Dockerfile и слои

Dockerfile — это рецепт сборки образа. Каждая инструкция = новый слой (кешируется). Изменение инструкции сбрасывает кеш всех последующих слоёв. Поэтому: редко меняемое (системные зависимости) — наверх, часто меняемое (исходный код) — вниз.

  • FROM — базовый образ; RUN — команда при сборке; COPY — скопировать файлы
  • WORKDIR — рабочая папка; ENV — переменная; EXPOSE — задокументировать порт
  • CMD — команда по умолчанию (переопределяется); ENTRYPOINT — фиксированная точка входа
  • Volume — постоянное хранилище, данные сохраняются при удалении контейнера

1. Что такое Dockerfile

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

Имя файла — всегда Dockerfile (с заглавной буквы и без расширения). Инструкции пишутся заглавными буквами, после них идут аргументы.

Зачем нужен Dockerfile? До того как появился Dockerfile, образы создавали вручную — запускали контейнер, вносили изменения, потом делали docker commit. Это неудобно, непрозрачно и не воспроизводимо. Dockerfile — это воспроизводимый рецепт: любой разработчик получит одинаковый образ из одного и того же Dockerfile.

Минимальный пример Dockerfile:

# Dockerfile
FROM ubuntu:22.04
COPY . /app
WORKDIR /app
RUN apt-get update && apt-get install -y python3
ENV PYTHONUNBUFFERED=1
CMD ["python3", "app.py"]

2. Слои образа и кеширование

Каждая инструкция в Dockerfile создаёт новый слой — промежуточное состояние файловой системы после выполнения этой инструкции.

Как работает кеш

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

Важно: изменение одной инструкции приводит к пересборке всех слоёв ниже неё. Поэтому стабильные инструкции (установка зависимостей) должны быть выше в Dockerfile, а часто меняемые (копирование кода) — ниже.

FROM scratch

Особый случай: FROM scratch создаёт образ с нуля, без какого-либо базового образа. Используется для создания минимальных образов — только самые необходимые файлы и библиотеки. Типичный сценарий — Go-бинарник, который компилируется статически.

3. Инструкции Dockerfile

FROM — базовый образ

Первая и обязательная инструкция. Задаёт родительский образ, на основе которого будет собран новый.

# Dockerfile
FROM ubuntu:22.04
FROM python:3.12-slim
FROM nginx:1.27-alpine
FROM scratch
Best practice: выбирайте минимальные базовые образы. Варианты slim, alpine значительно меньше ubuntu:latest — меньше потенциальных уязвимостей и быстрее скачивание.

LABEL — метаданные

Добавляет метаданные к образу: автор, версия, описание. Не влияет на работу, но полезно для документирования.

# Dockerfile
LABEL maintainer="you@example.com"
LABEL version="1.0"
LABEL description="My web app"

WORKDIR — рабочая директория

Задаёт рабочую директорию для последующих инструкций (RUN, COPY, CMD, ENTRYPOINT). Если директория не существует — создаёт её. Предпочтительнее, чем RUN mkdir && cd.

# Dockerfile
WORKDIR /app
# Теперь все пути относительны /app

COPY — копирование файлов

Копирует файлы и директории из контекста сборки (папки, откуда запускается docker build) в файловую систему образа. Если целевая директория не существует — создаёт её.

# Dockerfile
COPY index.html /usr/share/nginx/html/
COPY . /app
COPY requirements.txt .          # в текущий WORKDIR

RUN — команды при сборке

Выполняет команду в оболочке контейнера и фиксирует результат как новый слой. Используется для установки пакетов, настройки окружения, компиляции.

# Dockerfile
# Правильно: объединяем в одну RUN (один слой) + очищаем кеш пакетов
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
Минимизация слоёв: несколько команд RUN создают несколько слоёв. Объединяйте команды через && в один RUN — меньше слоёв, меньше итоговый размер образа.

ENV — переменные окружения

Устанавливает переменные среды, которые доступны и при сборке, и при работе контейнера.

# Dockerfile
ENV APP_ENV=production
ENV PORT=8080
ENV PYTHONUNBUFFERED=1

ARG — аргументы сборки

Переменные, которые передаются при сборке через docker build --build-arg. В отличие от ENV, недоступны в работающем контейнере.

# Dockerfile
ARG APP_VERSION=1.0
RUN echo "Building version $APP_VERSION"
# Терминал
docker build --build-arg APP_VERSION=2.0 .

EXPOSE — документирование порта

Указывает, что контейнер планирует использовать данный порт. Это только документация — реальный проброс порта делается флагом -p при docker run.

# Dockerfile
EXPOSE 80
EXPOSE 5432

CMD — команда запуска (переопределяемая)

Определяет команду по умолчанию при старте контейнера. В файле должна быть только одна инструкция CMD (последняя побеждает). Может быть переопределена аргументами docker run.

# Dockerfile
# Exec-форма (предпочтительная — не запускает shell)
CMD ["nginx", "-g", "daemon off;"]
CMD ["python3", "app.py"]

# Shell-форма (запускает через /bin/sh -c)
CMD python3 app.py

ENTRYPOINT — фиксированная точка входа

Определяет исполняемый файл, который всегда запускается при старте контейнера. Аргументы docker run добавляются к ENTRYPOINT, а не заменяют его. Переопределить можно только флагом --entrypoint.

# Dockerfile
ENTRYPOINT ["python3", "app.py"]
# docker run myimage --port 8080  →  python3 app.py --port 8080
CMD + ENTRYPOINT вместе: ENTRYPOINT задаёт команду, CMD — аргументы по умолчанию для неё. При запуске можно переопределить только аргументы (CMD-часть), а исполняемый файл остаётся.
# Dockerfile
ENTRYPOINT ["python3"]
CMD ["app.py"]
# docker run myimage other.py  →  python3 other.py

USER — пользователь выполнения

Устанавливает пользователя для выполнения последующих команд. Best practice: создавать отдельного непривилегированного пользователя вместо root.

# Dockerfile
RUN useradd -m appuser
USER appuser

VOLUME — точка монтирования

Создаёт точку монтирования. Данные в этой директории хранятся вне слоёв образа и сохраняются при удалении контейнера. При запуске без явного указания volume Docker создаёт анонимный volume автоматически.

# Dockerfile
VOLUME ["/data"]
VOLUME /var/lib/mysql

ADD — расширенная версия COPY

Похожа на COPY, но дополнительно умеет: распаковывать локальные .tar-архивы и скачивать файлы по URL. В большинстве случаев рекомендуется использовать COPY — она предсказуемее.

# Dockerfile
# Использовать только когда нужна распаковка tar:
ADD archive.tar.gz /app/

4. Оптимизация Dockerfile

Стратегия порядка слоёв

Кеш аннулируется начиная с изменённой строки. Правило: размещайте то, что меняется редко — выше; то, что меняется часто — ниже.

# Dockerfile
# Правильный порядок для Python-приложения:

FROM python:3.12-slim

WORKDIR /app

# 1. Сначала только requirements — меняется редко → долгий слой кешируется
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 2. Потом код приложения — меняется часто → пересборка только быстрого слоя
COPY . .

CMD ["python3", "app.py"]

Файл .dockerignore

Аналог .gitignore — исключает файлы из контекста сборки. Уменьшает размер контекста (ускоряет docker build) и предотвращает случайное попадание секретов в образ.

# .dockerignore
.git
.env
__pycache__
*.pyc
*.log
node_modules
.DS_Store

Очистка временных файлов

После установки пакетов apt/apk оставляют кеш — он увеличивает размер слоя. Очищайте его в той же инструкции RUN:

# Dockerfile
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

5. Persistence Layer в Docker

Persistence Layer (слой сохранения данных) — механизм долговременного хранения данных в архитектуре приложения.

Проблема: контейнеры по природе своей эфемерны. Любые данные внутри контейнера исчезают при его удалении. Для баз данных, загружаемых файлов, журналов это неприемлемо.

Три сценария, где нужен Persistence Layer

  • Хранение данных между перезапусками: БД не должна терять записи при перезапуске контейнера
  • Состояние приложения: пользовательские данные, конфиги, загруженные файлы
  • Отказоустойчивость: данные вне контейнера можно восстановить при сбое

Тома (Volumes)

Предпочтительный механизм. Управляются Docker-daemon, хранятся в специальной области Docker на хосте (/var/lib/docker/volumes/). Подходят для продакшна.

  • Создаются и управляются Docker, изолированы от файловой системы хоста
  • Могут быть именованными или анонимными
  • Поддерживают удалённые хранилища через драйверы
# Терминал
# Создать именованный volume
docker volume create mydata

# Использовать volume при запуске контейнера
docker run -v mydata:/app/data myimage

# Список volumes
docker volume ls

# Удалить volume
docker volume rm mydata

Bind Mounts (связанные монтирования)

Монтирует конкретную директорию хоста в контейнер. Полезно при разработке — изменения на хосте сразу видны в контейнере. Менее переносимо для продакшна (зависит от структуры файловой системы хоста).

# Терминал
# Монтировать текущую директорию в /app контейнера
docker run -v ./src:/app/src myimage

# Windows PowerShell — абсолютный путь
docker run -v C:\projects\myapp:/app myimage

6. Запуск базы данных в Docker

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

MySQL в Docker

# Терминал
docker run -d \
  --name mysqlDB \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  -e MYSQL_DATABASE=myapp \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

Разбор флагов:

  • -p 3306:3306 — проброс порта хост:контейнер
  • -e MYSQL_ROOT_PASSWORD — пароль root (обязателен для mysql-образа)
  • -e MYSQL_DATABASE — автоматически создать базу данных при запуске
  • -v mysql-data:/var/lib/mysql — именованный volume для данных MySQL

PostgreSQL в Docker

# Терминал
docker run -d \
  --name postgresDB \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=my-secret-pw \
  -e POSTGRES_DB=myapp \
  -e POSTGRES_USER=myuser \
  -v pg-data:/var/lib/postgresql/data \
  postgres:16-alpine
Никогда не хардкодьте пароли в Dockerfile! Инструкция ENV в Dockerfile попадает в историю слоёв образа и видна через docker history и docker inspect. Передавайте секреты только через -e при docker run или через Docker Secrets (для Swarm/Compose).
← К оглавлению урока