Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Docker и деплой
docker

Docker и деплой

Контейнеризация, docker-compose, production deployment

Открыть лабораториюv1.0Запускается локально из публичного репозитория

Docker и деплой FastAPI приложений

Контейнеризация решает проблему «на моей машине работает» и делает деплой предсказуемым. В этой теме вы научитесь упаковывать FastAPI в контейнер, запускать с зависимостями и деплоить в production.

#Что такое Docker?

Docker — платформа для запуска приложений в изолированных контейнерах.

Ваша машина          Production-сервер
┌──────────────┐     ┌──────────────┐
│   Docker     │     │   Docker     │
│  ┌────────┐  │     │  ┌────────┐  │
│  │  Контей │  │     │  │  Контей │  │
│  │  нер   │  │     │  │  нер   │  │
│  └────────┘  │     │  └────────┘  │
└──────────────┘     └──────────────┘
     Одинаковое поведение в обоих местах

Зачем:

  • Одинаковое окружение — development и production идентичны
  • Изоляция — каждый сервис в своём контейнере
  • Масштабирование — легко запустить несколько копий
  • Быстрый старт — новый разработчик запускает проект одной командой

#.dockerignore

Всегда начинайте с .dockerignore — как .gitignore, но для Docker. Исключает ненужные файлы из контекста сборки (ускоряет билд, уменьшает размер образа).

# Виртуальное окружение .venv .env # Git .git .gitignore # Python-кэш __pycache__/ *.pyc *.pyo *.pyd .Python # Тесты и документация tests/ *.md docs/ # IDE .vscode/ .idea/ *.swp # Docker Dockerfile docker-compose*.yml .dockerignore # CI/CD .github/ .gitlab-ci.yml # coverage .coverage htmlcov/ .pytest_cache/

Почему это важно:

  • Без .dockerignore Docker копирует всё, включая .git (может быть 500+ МБ)
  • .env с секретами не должен попадать в образ
  • Контекст сборки передаётся демону Docker — чем меньше, тем быстрее билд

#Dockerfile для FastAPI

#Базовый Dockerfile

FROM python:3.12-slim # Рабочая директория WORKDIR /app # Переменные окружения ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 # Установка зависимостей COPY pyproject.toml poetry.lock ./ RUN pip install --no-cache-dir poetry && \ poetry config virtualenvs.create false && \ poetry install --only main --no-interaction # Копирование кода приложения COPY . . # Непривилегированный пользователь (безопасность) RUN useradd --create-home --shell /bin/bash appuser USER appuser # Порт EXPOSE 8000 # Запуск CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Разбор ключевых строк:

  • python:3.12-slim — минимальный образ Python (меньше attack surface)
  • PYTHONDONTWRITEBYTECODE=1 — не создавать .pyc файлы
  • PYTHONUNBUFFERED=1 — логи выводятся сразу (без буферизации)
  • EXPOSE 8000 — документация: порт, на котором работает приложение
  • USER appuser — запуск не от root (безопасность)

#Оптимизированный Dockerfile (multi-stage)

Multi-stage сборка уменьшает размер образа: зависимости собираются в одном контейнере, в финальный образ попадают только готовые пакеты.

# Stage 1: Сборка зависимостей FROM python:3.12-slim as builder WORKDIR /app ENV PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 RUN pip install --no-cache-dir poetry COPY pyproject.toml poetry.lock ./ RUN poetry export -f requirements.txt --output requirements.txt --without-hashes RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt # Stage 2: Минимальный runtime FROM python:3.12-slim WORKDIR /app ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 # Копируем готовые wheel-пакеты из builder COPY --from=builder /wheels /wheels RUN pip install --no-cache /wheels/* # Непривилегированный пользователь RUN useradd --create-home --shell /bin/bash appuser USER appuser # Копируем код (пользователь appuser владеет файлами) COPY --chown=appuser:appuser . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Преимущества multi-stage:

  • Финальный образ не содержит poetry, компиляторы, исходники зависимостей
  • Размер: базовый ~500 МБ, multi-stage ~150 МБ

#docker compose

Важно: в современных версиях Docker используется docker compose (пробел, без дефиса) — это плагин, а не отдельная утилита. Ключ version больше не нужен.

#Для разработки

services: api: build: . ports: - "8000:8000" volumes: - .:/app env_file: - .env environment: - DATABASE_URL=postgresql://user:pass@db:5432/mydb - REDIS_URL=redis://redis:6379 depends_on: - db - redis command: uvicorn main:app --reload --host 0.0.0.0 --port 8000 db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data volumes: postgres_data: redis_data:

Ключевые моменты для разработки:

  • volumes: - .:/app — код монтируется в контейнер, изменение файлов сразу видно без пересборки
  • --reload — uvicorn перезапускается при изменении кода
  • ports — прокидываем порты, чтобы можно было подключиться с хост-машины
  • env_file — загружаем переменные из .env файла

#Для production

services: api: build: context: . dockerfile: Dockerfile restart: unless-stopped env_file: - .env environment: - ENVIRONMENT=production depends_on: db: condition: service_healthy redis: condition: service_started networks: - app-network healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s timeout: 10s retries: 3 start_period: 10s db: image: postgres:16-alpine restart: unless-stopped env_file: - .env volumes: - postgres_data:/var/lib/postgresql/data networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-user}"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data networks: - app-network nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - api networks: - app-network volumes: postgres_data: redis_data: networks: app-network: driver: bridge

Ключевые моменты для production:

  • restart: unless-stopped — автоматический перезапуск при падении (кроме ручной остановки)
  • healthcheck — Docker проверяет здоровье сервисов
  • condition: service_healthy — API стартует только после того, как БД готова
  • networks — изолированная сеть между сервисами (нет доступа извне)
  • :ro — nginx конфиг только для чтения (безопасность)
  • Нет ports у БД и Redis — они доступны только внутри Docker сети

#Nginx конфигурация

#nginx.conf

events { worker_connections 1024; } http { upstream api { server api:8000; } # HTTP → HTTPS redirect server { listen 80; server_name example.com; return 301 https://$server_name$request_uri; } # HTTPS server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Размер запроса client_max_body_size 10M; # Таймауты proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; location / { proxy_pass http://api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Статика location /static { alias /app/static; expires 30d; add_header Cache-Control "public, immutable"; } } }

Что здесь происходит:

  • HTTP → HTTPS — все запросы перенаправляются на защищённое соединение
  • upstream api — Nginx проксирует на контейнер api:8000 (по имени сервиса в Docker сети)
  • proxy_set_header — передаёт реальный IP и протокол приложению
  • client_max_body_size — ограничение на размер загружаемых файлов

#Переменные окружения

#.env.example

# Приложение SECRET_KEY=your-secret-key-change-in-production ENVIRONMENT=production DEBUG=false # База данных POSTGRES_USER=user POSTGRES_PASSWORD=secure-password POSTGRES_DB=mydb DATABASE_URL=postgresql://user:secure-password@db:5432/mydb # Redis REDIS_URL=redis://redis:6379 # Email SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=user SMTP_PASSWORD=password

#.env (для разработки)

SECRET_KEY=dev-secret-key-not-for-production ENVIRONMENT=development DEBUG=true POSTGRES_USER=user POSTGRES_PASSWORD=pass POSTGRES_DB=mydb DATABASE_URL=postgresql://user:pass@db:5432/mydb REDIS_URL=redis://redis:6379

#Загрузка переменных в приложении

from pydantic_settings import BaseSettings from functools import lru_cache class Settings(BaseSettings): secret_key: str environment: str = "production" debug: bool = False database_url: str redis_url: str smtp_host: str smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" model_config = {"env_file": ".env", "env_file_encoding": "utf-8"} @lru_cache() def get_settings() -> Settings: return Settings()

Примечание: в Pydantic v2 используется model_config вместо вложенного класса Config.

#Миграции в Docker

#Подход 1: Миграции при запуске контейнера

FROM python:3.12-slim WORKDIR /app ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 RUN pip install --no-cache-dir poetry COPY pyproject.toml poetry.lock ./ RUN poetry config virtualenvs.create false && \ poetry install --only main --no-interaction COPY . . RUN useradd --create-home --shell /bin/bash appuser USER appuser # Миграции перед запуском приложения CMD alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000

Минусы: миграции запускаются при каждом старте, сложнее контролировать.

#Подход 2: Отдельный сервис миграций (рекомендуется)

services: migrate: build: . env_file: - .env command: alembic upgrade head depends_on: db: condition: service_healthy restart: "no" # Выполняется один раз и завершается api: build: . env_file: - .env depends_on: migrate: condition: service_completed_successfully # API ждёт завершения миграций

Преимущества:

  • Миграции запускаются один раз, отдельно от приложения
  • Можно откатить миграции без пересборки
  • API гарантированно стартует с актуальной схемой БД

#Health checks

#Endpoint здоровья в FastAPI

from fastapi import FastAPI from pydantic import BaseModel import psycopg2 import redis app = FastAPI() class HealthResponse(BaseModel): status: str database: str redis: str @app.get("/health", response_model=HealthResponse) def health_check(): db_status = "healthy" redis_status = "healthy" # Проверка БД try: conn = psycopg2.connect(DATABASE_URL) conn.close() except Exception as e: db_status = f"unhealthy: {str(e)}" # Проверка Redis try: r = redis.Redis.from_url(REDIS_URL) r.ping() except Exception as e: redis_status = f"unhealthy: {str(e)}" overall = "healthy" if db_status == "healthy" and redis_status == "healthy" else "unhealthy" return HealthResponse( status=overall, database=db_status, redis=redis_status )

Как Docker использует healthcheck:

healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s # Проверка каждые 30 секунд timeout: 10s # Таймаут на выполнение retries: 3 # Сколько неудачных попыток до 'unhealthy' start_period: 10s # Время на старт (не считает failed проверки)

#Логирование в Docker

#Ротация логов в compose

services: api: build: . logging: driver: "json-file" options: max-size: "10m" # Максимальный размер файла max-file: "3" # Количество файлов для ротации

#Structured logging (JSON)

import logging import json class JSONFormatter(logging.Formatter): def format(self, record): log_record = { "level": record.levelname, "message": record.getMessage(), "timestamp": self.formatTime(record), "module": record.module, } if record.exc_info: log_record["exc_info"] = self.formatException(record.exc_info) return json.dumps(log_record, ensure_ascii=False) handler = logging.StreamHandler() handler.setFormatter(JSONFormatter()) logging.basicConfig(level=logging.INFO, handlers=[handler])

Зачем JSON-логи:

  • Агрегаторы логов (ELK, Loki) парсят структурированные данные
  • Можно фильтровать по уровню, модулю, времени
  • Легче искать ошибки в production

#Деплой на сервер

#Подготовка сервера

# Установка Docker (скрипт устанавливает и Docker, и Compose плагин) curl -fsSL https://get.docker.com | sh # Добавить пользователя в группу docker (чтобы не использовать sudo) sudo usermod -aG docker $USER # Перелогиниться или: newgrp docker # Проверить установку docker --version docker compose version # Клонирование проекта git clone <repository> /opt/myapp cd /opt/myapp # Создать .env из шаблона cp .env.example .env nano .env # Заполнить реальные значения

#Запуск

# Первый запуск (сборка и запуск) docker compose up -d --build # Просмотр логов docker compose logs -f # Просмотр логов конкретного сервиса docker compose logs -f api # Статус сервисов docker compose ps # Остановка (сохраняя данные) docker compose down # Остановка с удалением volumes (осторожно!) docker compose down -v

#Основные команды

КомандаОписание
docker compose up -dЗапуск всех сервисов в фоне
docker compose up -d --buildПересборка и запуск
docker compose downОстановка сервисов
docker compose logs -fПросмотр логов в реальном времени
docker compose psСтатус контейнеров
docker compose exec api bashВойти в контейнер api
docker compose restart apiПерезапустить конкретный сервис

#CI/CD с деплоем

#.gitlab-ci.yml

stages: - test - build - deploy test: stage: test image: python:3.12-slim script: - pip install poetry - poetry install - poetry run pytest --tb=short - poetry run ruff check . deploy: stage: deploy image: docker:latest script: - docker compose build - docker compose push - ssh user@server "cd /opt/myapp && git pull && docker compose up -d --build" only: - main

#Деплой через SSH

# На сервере в /opt/myapp: # Обновить код и перезапустить cd /opt/myapp && \ git pull && \ docker compose up -d --build && \ docker compose logs -f --tail=50

#Мониторинг

#Prometheus + Grafana

services: prometheus: image: prom/prometheus volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro ports: - "9090:9090" networks: - monitoring grafana: image: grafana/grafana ports: - "3000:3000" environment: GF_SECURITY_ADMIN_PASSWORD: admin volumes: - grafana_data:/var/lib/grafana networks: - monitoring volumes: grafana_data: networks: monitoring: driver: bridge

#Частые ошибки

#Ошибка 1: Запуск от root

# ❌ Плохо: контейнер работает от root CMD uvicorn main:app --host 0.0.0.0 # ✅ Хорошо: непривилегированный пользователь RUN useradd --create-home --shell /bin/bash appuser USER appuser CMD uvicorn main:app --host 0.0.0.0

#Ошибка 2: Нет .dockerignore

Без .dockerignore Docker передаёт всю директорию демону:

  • .git может весить 500+ МБ — медленная передача
  • .env с паролями попадёт в образ
  • .venv увеличит размер

Решение: всегда создавайте .dockerignore перед Dockerfile.

#Ошибка 3: Неэффективное кэширование

# ❌ Плохо: COPY . . до установки зависимостей # При любом изменении кода зависимости переустанавливаются COPY . . RUN pip install -r requirements.txt # ✅ Хорошо: зависимости устанавливаются до копирования кода COPY pyproject.toml poetry.lock ./ RUN poetry install --only main --no-interaction COPY . .

Как работает кэш Docker:

  • Каждый RUN, COPY, ADD создаёт слой
  • Если инструкция не изменилась — Docker использует кэш
  • Зависимости меняются редко → слой кэшируется
  • Код меняется часто → не вызывает переустановку зависимостей

#Ошибка 4: docker-compose вместо docker compose

# ❌ Устаревший синтаксис (Compose V1, больше не поддерживается) docker-compose up -d # ✅ Современный синтаксис (Compose V2, плагин) docker compose up -d

#Ошибка 5: Указание version в compose файле

# ❌ Устарело (version не нужен с Compose V2) version: '3.8' services: api: build: . # ✅ Современный синтаксис services: api: build: .

#Чеклист production

  • .dockerignore создан
  • Multi-stage Dockerfile
  • Непривилегированный пользователь (USER appuser)
  • Health checks для всех сервисов
  • Логирование с ротацией (max-size, max-file)
  • Переменные окружения через .env (не захардкожены)
  • HTTPS через Nginx (Let's Encrypt)
  • restart: unless-stopped для автомата
  • Мониторинг (Prometheus/Grafana)
  • Бэкапы БД настроены

#Исполняемая лаборатория

Лаборатория «Multi-stage build, slim, non-root user» доступна в публичном GitLab. Она проверяет multi-stage Dockerfile с разделением builder и runtime, python:3.12-slim базовый образ, непривилегированного пользователя через USER appuser, .dockerignore и порядок слоёв для кэширования зависимостей.

Склонируйте репозиторий и запустите тесты из его корня:

git clone --branch v1.0 --depth 1 https://gitlab.potapov.me/courses/fastapi_pro.git cd fastapi_pro uv sync --group test uv run --group test pytest docker/tests

Далее: CI/CD и мониторинг