✅ Решения заданий урока 04

⚡ Ключевые файлы

# Dockerfile.multistage
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
    && rm -rf /var/lib/apt/lists/*
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"]

Решение задания 1: Multistage vs Single-stage

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

# терминал
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
RUN apt-get update && apt-get install -y build-essential \
    && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

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

# Dockerfile.multistage

# Стадия сборки: компилируем зависимости
FROM python:3.12-slim AS builder
WORKDIR /app
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, без build-essential
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 build -t myapp:multistage -f Dockerfile.multistage .
docker build -t myapp:singlestage -f Dockerfile.singlestage .

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

# Запуск
docker run -d --name multistage_app -p 5000:5000 myapp:multistage
docker run -d --name singlestage_app -p 5001:5001 myapp:singlestage

# Проверка: оба отвечают {"message": "Hello, World!"}
# http://localhost:5000
# http://localhost:5001

# Логи
docker logs multistage_app
docker logs singlestage_app

# Уборка
docker stop multistage_app singlestage_app
docker rm multistage_app singlestage_app
Образ с multistage примерно в 2–3 раза меньше. Это происходит потому, что build-essential (~200 МБ) остался только в промежуточной стадии builder и не вошёл в финальный слой.

Решение задания 2: MediaWiki + MariaDB

# терминал
mkdir mediawiki-compose && cd mediawiki-compose
# docker-compose.yml
# MediaWiki с MariaDB — современный стиль (без version:, без links:)

services:
  mediawiki:
    image: mediawiki
    restart: always
    ports:
      - "8080:80"
    volumes:
      - wiki_images:/var/www/html/images
      # После настройки раскомментировать:
      # - ./LocalSettings.php:/var/www/html/LocalSettings.php
    networks:
      - wikinet
    depends_on:
      database:
        condition: service_healthy

  database:
    image: mariadb:11
    restart: always
    environment:
      MYSQL_DATABASE: my_wiki
      MYSQL_USER: wikiuser
      MYSQL_PASSWORD: example
      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
    volumes:
      - wiki_db:/var/lib/mysql
    networks:
      - wikinet
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 30s
      timeout: 5s
      retries: 5

volumes:
  wiki_images:
  wiki_db:

networks:
  wikinet:
# терминал

docker compose up -d
docker compose ps           # дождаться "healthy" у database
docker compose logs mediawiki

# Открыть http://localhost:8080 — настроить MediaWiki
# После настройки скачать LocalSettings.php,
# раскомментировать volume в compose, перезапустить:
docker compose restart mediawiki

Решение задания 3: WordPress + MySQL

# терминал
mkdir wordpress-compose && cd wordpress-compose
# docker-compose.yml
# WordPress + MySQL — современный стиль с healthcheck

services:
  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootsecret
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - wpnet
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost",
             "-u", "root", "-prootsecret"]
      interval: 30s
      timeout: 5s
      retries: 5

  wordpress:
    image: wordpress:latest
    restart: always
    ports:
      - "8081:80"
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html
    networks:
      - wpnet

volumes:
  db_data:
  wp_data:

networks:
  wpnet:
# терминал

docker compose up -d

# Наблюдаем: wordpress ждёт healthcheck от db
docker compose ps

# После healthy у db — WordPress стартует
# Открыть http://localhost:8081 — установить WordPress

# Проверка сохранности данных:
docker compose down
docker compose up -d
# Данные сохранились в томах db_data и wp_data
Порт 8081 использован вместо 80, чтобы не конфликтовать с другими сервисами на хосте.