💻 Примеры: Multistage Dockerfile и Docker Compose

⚡ Два ключевых примера

# Dockerfile.multistage  — Python/Flask multistage

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
COPY app.py .

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"]
# docker-compose.yml  — web (nginx) + db (postgres)

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - db
    networks:
      - appnet

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - appnet

volumes:
  pgdata:

networks:
  appnet:

Пример 1: Multistage Dockerfile для Flask-приложения

Сравним два подхода: одностадийный и многостадийный Dockerfile для одного и того же Flask-приложения.

Структура проекта

# терминал
mkdir flask-multistage && cd flask-multistage

Файл приложения:

# app.py

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello():
    return jsonify(message="Hello, World!")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# requirements.txt

Flask==3.0.3
Werkzeug==3.0.3

Одностадийный Dockerfile (для сравнения)

# Dockerfile.singlestage

FROM python:3.12-slim
WORKDIR /app
# Копируем всё, включая инструменты pip
RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

Многостадийный Dockerfile

# Dockerfile.multistage

# ---- Стадия builder: установка зависимостей ----
FROM python:3.12-slim AS builder
WORKDIR /app
# build-essential нужен только для компиляции, не попадёт в финальный образ
RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
    && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
# --user устанавливает в ~/.local — легко скопировать в финальный образ
RUN pip install --user --no-cache-dir -r requirements.txt
COPY app.py .

# ---- Финальный образ: только runtime ----
FROM python:3.12-slim
WORKDIR /app
# Копируем установленные пакеты из стадии builder
COPY --from=builder /root/.local /root/.local
# Копируем только исходник приложения
COPY --from=builder /app/app.py .
# Добавляем ~/.local/bin в PATH, чтобы flask/gunicorn были доступны
ENV PATH=/root/.local/bin:$PATH
EXPOSE 5000
CMD ["python", "app.py"]

Сборка и сравнение размеров

# терминал

# Собираем одностадийный образ
docker build -t myapp:singlestage -f Dockerfile.singlestage .

# Собираем многостадийный образ
docker build -t myapp:multistage -f Dockerfile.multistage .

# Сравниваем размеры
docker images myapp:singlestage
docker images myapp:multistage

Ожидаемый результат: образ с multistage примерно в 2–3 раза меньше по размеру.

Запуск и проверка

# терминал

docker run -d --name multistage_app -p 5000:5000 myapp:multistage
docker run -d --name singlestage_app -p 5001:5001 myapp:singlestage

# Проверка в браузере или curl:
# http://localhost:5000  — multistage
# http://localhost:5001  — singlestage

# Просмотр логов
docker logs multistage_app
docker logs singlestage_app

# Остановка
docker stop multistage_app singlestage_app
docker rm multistage_app singlestage_app

Пример 2: Docker Compose — веб-сервер и база данных

Классическая связка: nginx как фронтенд + PostgreSQL как база данных.

Структура проекта

# терминал
mkdir compose-webdb && cd compose-webdb
mkdir html
# html/index.html

<!DOCTYPE html>
<html>
<head><title>Docker Compose Demo</title></head>
<body>
  <h1>Привет от nginx в Docker Compose!</h1>
</body>
</html>

docker-compose.yml

# docker-compose.yml

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    depends_on:
      - db
    networks:
      - appnet

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - appnet

volumes:
  pgdata:

networks:
  appnet:

Запуск

# терминал

# Запустить все сервисы в фоне
docker compose up -d

# Посмотреть статус
docker compose ps

# Открыть в браузере: http://localhost:8080

# Логи
docker compose logs web
docker compose logs db

# Остановить и очистить
docker compose down

Пример 3: Docker Compose — Django-приложение с PostgreSQL

Более сложный пример из лекции: кастомная сборка приложения + БД с зависимостью.

# docker-compose.yml

services:
  web:
    build: ./web                           # собрать из ./web/Dockerfile
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./web:/code                        # bind mount для hot reload
    ports:
      - "8000:8000"
    depends_on:
      - db
    environment:
      - DJANGO_DB_HOST=db
      - DJANGO_DB_PORT=5432
      - DJANGO_DB_NAME=mydatabase
      - DJANGO_DB_USER=myuser
      - DJANGO_DB_PASSWORD=mypassword

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydatabase
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword

volumes:
  pgdata:
# web/Dockerfile

FROM python:3.12-slim
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

Запуск с пересборкой

# терминал

# Собрать образы и запустить
docker compose up --build -d

# Выполнить миграции Django
docker compose exec web python manage.py migrate

# Создать суперпользователя
docker compose exec web python manage.py createsuperuser

Пример 4: depends_on с healthcheck

Правильный способ дождаться готовности базы данных перед стартом приложения.

# docker-compose.yml

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootsecret
      MYSQL_DATABASE: mydb
      MYSQL_USER: appuser
      MYSQL_PASSWORD: appsecret
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      # Проверяем, что MySQL принимает подключения
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s      # проверять каждые 30 секунд
      timeout: 5s        # таймаут одной проверки
      retries: 5         # количество попыток до статуса "unhealthy"

  app:
    build: ./app
    ports:
      - "5000:5000"
    depends_on:
      db:
        condition: service_healthy   # ждать, пока DB пройдёт healthcheck
    environment:
      DB_HOST: db
      DB_USER: appuser
      DB_PASSWORD: appsecret
      DB_NAME: mydb

volumes:
  mysql_data:
# терминал

docker compose up -d
# Сначала запустится db, app дождётся healthcheck
docker compose ps   # смотрим статус: "healthy" у db
docker compose logs app   # приложение стартует только после healthy