FastAPI Deploy: пошаговое руководство с Docker, Nginx и SSL | AdminWiki

Полное руководство по FastAPI deploy: от локальной разработки до продакшена

18 декабря 2025 12 мин. чтения #Gunicorn #deploy #devops #docker #fastapi #nginx #python #ssl

Представь, что ты написал крутое FastAPI приложение — оно быстрое, типизированное и покрыто тестами. Но теперь возникает главный вопрос: как вывести его в продакшен так, чтобы оно работало стабильно, безопасно и выдерживало нагрузку? Давай разберем весь путь fastapi deploy от локальной машины до облачного сервера.

Ключевой принцип: продакшен окружение должно максимально отличаться от разработки. Мы минимизируем переменные и стандартизируем процесс.

Подготовка приложения к деплою

Перед тем как начать развертывание FastAPI, нужно подготовить само приложение. Вот что обязательно должно быть:

  • requirements.txt или pyproject.toml с зафиксированными версиями зависимостей
  • Отдельные конфиги для разработки и продакшена
  • Логирование, настроенное для продакшена
  • Корректная обработка ошибок
  • Проверенные миграции базы данных (если используются)

Структура проекта для деплоя

bash
my_fastapi_app/
├── app/
│   ├── __init__.py
│   ├── main.py          # Точка входа FastAPI
│   ├── api/
│   ├── core/
│   │   ├── config.py    # Конфигурация
│   │   └── logger.py    # Логирование
│   └── db/
├── requirements.txt
├── Dockerfile           # Для контейнеризации
├── docker-compose.yml   # Для оркестрации
├── nginx/
│   └── nginx.conf       # Конфиг обратного прокси
└── scripts/
    └── start.sh         # Скрипт запуска

Контейнеризация с Docker

Docker — стандарт де-факто для деплоя Python приложений. Он гарантирует, что приложение будет работать одинаково в любой среде.

Оптимизированный Dockerfile для FastAPI

docker
# Базовый образ Python
FROM python:3.11-slim as builder

# Устанавливаем зависимости системы
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    && rm -rf /var/lib/apt/lists/*

# Рабочая директория
WORKDIR /app

# Копируем зависимости
COPY requirements.txt .

# Создаем виртуальное окружение и устанавливаем зависимости
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# Финальный образ
FROM python:3.11-slim

# Копируем виртуальное окружение из builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Создаем непривилегированного пользователя
RUN useradd -m -u 1000 fastapi-user
USER fastapi-user

# Рабочая директория
WORKDIR /app

# Копируем код приложения
COPY --chown=fastapi-user:fastapi-user ./app ./app

# Порт приложения
EXPOSE 8000

# Команда запуска
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Никогда не запускай приложение от root в контейнере! Создавай отдельного пользователя для безопасности.

Продакшен-сервер с Gunicorn + Uvicorn

В разработке мы используем uvicorn напрямую, но для продакшена нужен Gunicorn как менеджер процессов. Он обеспечивает:

  • Запуск нескольких воркеров для обработки параллельных запросов
  • Автоматический перезапуск упавших воркеров
  • Балансировку нагрузки между воркерами
  • Graceful shutdown при получении сигналов

Конфигурация Gunicorn для FastAPI

python
# gunicorn_conf.py
import multiprocessing
import os

# Количество воркеров
workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)

host = os.getenv("HOST", "0.0.0.0")
port = os.getenv("PORT", "8000")
bind_env = os.getenv("BIND", None)

if bind_env:
    use_bind = bind_env
else:
    use_bind = f"{host}:{port}"

# Расчет количества воркеров
cores = multiprocessing.cpu_count()
workers_per_core = float(workers_per_core_str)
default_web_concurrency = workers_per_core * cores + 1

if web_concurrency_str:
    web_concurrency = int(web_concurrency_str)
    assert web_concurrency > 0
else:
    web_concurrency = int(default_web_concurrency)

# Gunicorn конфиг
worker_class = "uvicorn.workers.UvicornWorker"
workers = web_concurrency
bind = use_bind
keepalive = 120
errorlog = "-"
loglevel = "info"
accesslog = "-"
graceful_timeout = 120
timeout = 120
worker_tmp_dir = "/dev/shm"

Обновленный CMD в Dockerfile

docker
# Заменяем последнюю строку в Dockerfile:
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "gunicorn_conf.py", "app.main:app"]

Настройка Nginx как обратного прокси

Nginx выполняет критически важные функции в нашем fastapi деплое:

  • Принимает входящие HTTP/HTTPS запросы
  • Раздает статические файлы (гораздо эффективнее, чем через Python)
  • Обеспечивает сжатие gzip
  • Защищает от некоторых типов DDoS атак
  • Балансирует нагрузку между несколькими инстансами приложения

Конфигурация Nginx для FastAPI

config
# nginx/nginx.conf
upstream fastapi_app {
    server app:8000;  # Docker service name
    keepalive 32;
}

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    
    # Логи
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    
    # Безопасные заголовки
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Размер загружаемых файлов
    client_max_body_size 10M;
    
    # Статические файлы
    location /static/ {
        alias /app/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Проксирование к FastAPI
    location / {
        proxy_pass http://fastapi_app;
        
        # Прокси заголовки
        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;
        
        # Таймауты
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Полный docker-compose для продакшена

yaml
# docker-compose.prod.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: fastapi_app
    restart: unless-stopped
    env_file:
      - .env.production
    volumes:
      - ./app:/app/app:ro
      - static_volume:/app/static
    networks:
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    depends_on:
      - postgres

  nginx:
    image: nginx:alpine
    container_name: nginx_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
      - static_volume:/app/static
    networks:
      - backend
    depends_on:
      - app

  postgres:
    image: postgres:15-alpine
    container_name: postgres_db
    restart: unless-stopped
    env_file:
      - .env.production
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  postgres_data:
  static_volume:

Настройка SSL/TLS с Let's Encrypt

HTTPS — обязательное требование для любого продакшен приложения. Используем Certbot для автоматического получения сертификатов.

Скрипт настройки SSL

bash
#!/bin/bash
# scripts/setup_ssl.sh

domain="yourdomain.com"
email="admin@yourdomain.com"

# Создаем директории для Certbot
mkdir -p certbot/conf certbot/www

# Получаем сертификат
docker run -it --rm \
  -v "$(pwd)/certbot/conf:/etc/letsencrypt" \
  -v "$(pwd)/certbot/www:/var/www/certbot" \
  certbot/certbot certonly \
  --webroot --webroot-path /var/www/certbot \
  --agree-tos \
  --no-eff-email \
  --email "${email}" \
  -d "${domain}" \
  -d "www.${domain}"

# Обновляем nginx конфиг для HTTPS
cat > nginx/nginx_ssl.conf << EOF
server {
    listen 80;
    server_name ${domain} www.${domain};
    return 301 https://\$server_name\$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ${domain} www.${domain};
    
    ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
    
    # Остальная конфигурация как в примере выше...
}
EOF

# Скрипт для автообновления сертификатов
echo "0 0 1 * * docker run --rm -v $(pwd)/certbot/conf:/etc/letsencrypt -v $(pwd)/certbot/www:/var/www/certbot certbot/certbot renew >> /var/log/certbot.log 2>&1" | crontab -

Мониторинг и логирование

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

Компонент Инструмент Что мониторит
Логи приложения ELK Stack / Loki Ошибки, запросы, время выполнения
Метрики Prometheus + Grafana CPU, память, кол-во запросов, latency
Трассировка Jaeger / OpenTelemetry Распределенная трассировка запросов
Доступность UptimeRobot / Pingdom Uptime, response time

Добавляем метрики Prometheus

python
# app/core/metrics.py
from prometheus_client import Counter, Histogram, generate_latest
from fastapi import Response
from fastapi.routing import APIRoute
import time

REQUEST_COUNT = Counter(
    'http_requests_total',
    'Total HTTP Requests',
    ['method', 'endpoint', 'http_status']
)

REQUEST_LATENCY = Histogram(
    'http_request_duration_seconds',
    'HTTP request latency in seconds',
    ['method', 'endpoint']
)

class PrometheusMiddleware:
    def __init__(self, app):
        self.app = app
    
    async def __call__(self, scope, receive, send):
        if scope['type'] != 'http':
            return await self.app(scope, receive, send)
        
        method = scope['method']
        path = scope['path']
        
        start_time = time.time()
        
        async def send_wrapper(message):
            if message['type'] == 'http.response.start':
                status_code = message['status']
                REQUEST_COUNT.labels(
                    method=method,
                    endpoint=path,
                    http_status=status_code
                ).inc()
                
                REQUEST_LATENCY.labels(
                    method=method,
                    endpoint=path
                ).observe(time.time() - start_time)
            
            await send(message)
        
        await self.app(scope, receive, send_wrapper)

# В main.py
from app.core.metrics import PrometheusMiddleware, generate_latest

app = FastAPI()
app.add_middleware(PrometheusMiddleware)

@app.get("/metrics")
async def metrics():
    return Response(generate_latest())

CI/CD пайплайн для автоматического деплоя

Автоматизируем процесс деплоя FastAPI приложения с помощью GitHub Actions.

yaml
# .github/workflows/deploy.yml
name: Deploy FastAPI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-asyncio httpx
    
    - name: Run tests
      run: |
        pytest -v --cov=app --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    
    - name: Login to DockerHub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: |
          yourusername/fastapi-app:latest
          yourusername/fastapi-app:${{ github.sha }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
    
    - name: Deploy to server
      uses: appleboy/ssh-action@v0.1.5
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USER }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          cd /opt/fastapi-app
          docker-compose pull
          docker-compose up -d
          docker system prune -f

Лучшие практики безопасности

Безопасность — не фича, а обязательное требование. Эти пункты нельзя игнорировать.
  • Секреты в .env файлах: Никогда не коммитьте .env файлы. Используйте .env.example как шаблон
  • Обновления безопасности: Регулярно обновляйте базовые образы и зависимости
  • Минимальные привилегии: Запускайте контейнеры от непривилегированных пользователей
  • Сканирование уязвимостей: Используйте Trivy или Docker Scout для сканирования образов
  • Rate limiting: Настройте ограничение запросов на уровне Nginx или в приложении
  • CORS: Точно настройте CORS политики, не используйте allow_origins=["*"] в продакшене

Сравнение методов деплоя

Метод Плюсы Минусы Когда использовать
Docker Compose Простота, локальное тестирование, все в одном месте Нет автомасштабирования, single point of failure Стартапы, пет-проекты, staging окружение
Kubernetes Автомасштабирование, отказоустойчивость, продвинутый мониторинг Сложность настройки, overhead Крупные проекты, микросервисы, высокие нагрузки
Serverless (AWS Lambda) Автомасштабирование, pay-per-use, минимальный оверхеад Cold starts, ограничения времени выполнения API с переменной нагрузкой, event-driven архитектура
PaaS (Heroku, Render) Максимальная простота, managed БД, автоматический деплой Дорого на масштабе, меньше контроля Прототипы, MVP, небольшие проекты

FAQ: Частые вопросы по деплою FastAPI

Сколько воркеров Gunicorn нужно для моего приложения?

Общее правило: (2 * количество ядер) + 1. Для 4-ядерного сервера: 9 воркеров. Но тестируй под нагрузкой! I/O-bound приложения могут требовать больше воркеров, CPU-bound — меньше. Начинай с формулы и корректируй по метрикам.

Как обрабатывать статические файлы в FastAPI?

В продакшене никогда не используй StaticFiles из FastAPI. Настрой Nginx на раздачу статики: это в 100+ раз эффективнее. Храни статику в volume и монтируй в оба контейнера (app и nginx).

Как деплоить несколько FastAPI сервисов на одном сервере?

Используй Docker Compose с разными портами для каждого сервиса и настрой Nginx как reverse proxy с location блоками. Или лучше — переходи на Kubernetes с Ingress контроллером для proper routing.

Мой деплой работает, но приложение медленное. Что проверять?

1) Логи Nginx на 5xx ошибки 2) Мониторинг CPU/памяти контейнеров 3) Задержки в БД (slow queries) 4) Сетевую задержку между сервисами 5) Gunicorn timeout значения (увеличить если нужно). Используй APM инструменты вроде Sentry или Datadog.

Как сделать zero-downtime деплой?

1) Используй health checks в Docker Compose/Kubernetes 2) Настройте Gunicorn graceful timeout (30-60 секунд) 3) При деплое: запускай новый контейнер, дождись health check, затем останавливай старый 4) Используй blue-green deployment если критично.

Чеклист успешного деплоя

  • HTTPS работает (проверь SSL Labs rating)
  • Health check endpoint возвращает 200
  • Статические файлы отдаются через Nginx
  • Логи пишутся в stdout и собираются
  • Мониторинг настроен (метрики, алерты)
  • Резервные копии БД настроены
  • CI/CD пайплайн тестирует и деплоит автоматически
  • Документация API доступна по /docs и /redoc

Поздравляю! Теперь у тебя есть полное понимание, как делать профессиональный fastapi deploy. Помни: продакшен — это не просто "работает на сервере". Это надежность, безопасность, мониторинг и возможность масштабироваться. Начинай с Docker Compose, добавляй мониторинг, затем автоматизируй деплой. Каждый следующий проект будет деплоиться быстрее и надежнее!

Поделиться:
Сохранить гайд? В закладки браузера