📖 Теория: Multistage Dockerfile и Docker Compose
⚡ Теория в двух абзацах
Multistage build — это несколько секций FROM в одном Dockerfile. Каждая секция — отдельная стадия. Финальная стадия копирует из предыдущих только то, что нужно для запуска (COPY --from=build). Инструменты сборки (gcc, maven, pip install) остаются в промежуточных слоях и не попадают в финальный образ.
Docker Compose — инструмент для запуска нескольких контейнеров как единого приложения. Вы описываете все сервисы, их образы, порты, тома, переменные окружения и сети в одном YAML-файле, а затем поднимаете всё командой docker compose up. Compose v2 встроен в Docker и не требует отдельной установки.
Часть 1: Multi Stage Build
Зачем нужен Multistage build
Типичная проблема: чтобы собрать приложение, нам нужны компиляторы, пакетные менеджеры, тестовые инструменты. Но в продакшне они не нужны — они только увеличивают образ и расширяют поверхность атаки.
До Multistage build разработчики решали это скриптами-оберёртками: собирали в одном контейнере, копировали артефакт наружу, создавали новый образ. Это было неудобно и сложно в CI/CD.
Multistage build решает проблему внутри одного Dockerfile:
- Промежуточные стадии содержат все инструменты сборки — они кешируются Docker и не попадают в финальный образ.
- Финальная стадия копирует только скомпилированные артефакты (
COPY --from=). - Финальный образ маленький, без лишних зависимостей, с минимальной поверхностью атаки.
Синтаксис: FROM … AS и COPY --from
Каждая стадия начинается с инструкции FROM. Чтобы можно было сослаться на стадию позже, ей дают имя через AS:
# Dockerfile
# Стадия 1: сборка (имя "builder")
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
# Стадия 2: финальный образ
FROM python:3.12-slim
WORKDIR /app
# Копируем только установленные пакеты из стадии builder
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/app.py .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 5000
CMD ["python", "app.py"]
Ключевые моменты:
FROM … AS builder— имя стадии (произвольное, обычноbuild,builder,test)COPY --from=builder /путь/в/стадии /путь/в/финальном— копируем артефакты- Финальная стадия не знает ничего о предыдущих, кроме скопированных файлов
- Инструменты сборки (apt, pip, gcc) остаются только в промежуточных слоях
Пример: Java-приложение (Maven + OpenJDK)
Из лекции: классический пример — собрать JAR с Maven, запустить только через JRE:
# Dockerfile
# Стадия сборки: нужен Maven и JDK
FROM maven:3.6.0-jdk-8 AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean package -Dmaven.test.skip=true
# Стадия запуска: нужен только JRE (образ намного меньше)
FROM openjdk:8-jdk-slim
COPY --from=build /home/app/target/project-0.0.1-SNAPSHOT.jar \
/usr/local/lib/accounting.jar
EXPOSE 8099
ENTRYPOINT ["java", "-jar", "/usr/local/lib/accounting.jar"]
Maven-образ весит ~500 МБ. OpenJDK slim — ~200 МБ. Финальный образ не содержит Maven вообще.
Пример: Python-приложение с build-зависимостями
Если пакеты требуют компиляции C-расширений (psycopg2, cryptography и т. д.), стадия сборки нужна:
# Dockerfile
# Стадия builder: компилируем зависимости
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
# Финальный образ: только runtime
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/app.py .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 5000
CMD ["python", "app.py"]
build-essential (gcc, make) и кеш apt остаются в стадии builder и не попадают в финальный образ.
Часть 2: Docker Compose
Что такое Docker Compose
Docker Compose — инструмент для определения и запуска многоконтейнерных Docker-приложений. Входит в состав Docker (начиная с Docker Desktop и Docker Engine 20.10+).
Вместо того чтобы запускать каждый контейнер отдельной командой docker run с десятками флагов, вы описываете всё приложение в одном YAML-файле (docker-compose.yml) и управляете им одной командой.
Структура docker-compose.yml
Compose-файл содержит три основных раздела:
- services — описание каждого контейнера (образ, порты, тома, переменные, зависимости)
- volumes — именованные тома для хранения данных
- networks — пользовательские сети для изоляции и связи сервисов
# docker-compose.yml
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
networks:
- webnet
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: mydb
volumes:
- db_data:/var/lib/mysql
networks:
- webnet
volumes:
db_data:
networks:
webnet:
Ключевые поля сервиса
| Поле | Описание | Пример |
|---|---|---|
image |
Готовый образ из реестра | image: postgres:16 |
build |
Путь к Dockerfile для сборки | build: ./app |
ports |
Проброс порта хост:контейнер | - "8080:80" |
volumes |
Тома или bind mounts | - db_data:/var/lib/mysql |
environment |
Переменные окружения | POSTGRES_PASSWORD: secret |
depends_on |
Зависимость от других сервисов | depends_on: [db] |
networks |
Сети, к которым подключён сервис | - webnet |
restart |
Политика перезапуска | restart: always |
command |
Переопределить CMD образа | command: python manage.py runserver |
Типы сетей в Docker
Docker поддерживает несколько типов сетей. В Docker Compose чаще всего используется bridge:
По умолчанию. Создаёт изолированный сетевой мост. Контейнеры в одной bridge-сети видят друг друга по имени сервиса. Внешний доступ — только через
ports.
Контейнер использует сетевой стек хоста напрямую. Нет изоляции — порты контейнера = порты хоста. Используется для снижения сетевой задержки.
Контейнер полностью изолирован от сети. Используется для batch-задач без сетевого взаимодействия.
depends_on и healthcheck
depends_on задаёт порядок запуска: web стартует после db. Но «запущен» ≠ «готов принимать подключения»: MySQL после старта ещё несколько секунд инициализируется.
Решение — использовать healthcheck совместно с depends_on: condition: service_healthy:
# docker-compose.yml
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: mydb
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 5s
retries: 5
web:
build: ./app
depends_on:
db:
condition: service_healthy
Теперь web стартует только когда MySQL ответит на ping — то есть реально готов к подключениям.
Сборка из исходников: поле build
Если сервис нужно собрать из Dockerfile, используйте build вместо image:
# docker-compose.yml
services:
web:
build: ./web # путь к директории с Dockerfile
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
При docker compose up --build Compose сначала соберёт образ из Dockerfile в ./web, затем запустит контейнер.