← Назад к вопросам
Как отдаешь статические файлы на production?
2.0 Middle🔥 131 комментариев
#DevOps и инфраструктура
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Раздача статических файлов на Production
Доставка статических файлов (CSS, JS, изображения, шрифты) — критична для производительности. На production это делается правильно, а не через приложение.
1. НЕ используй FastAPI/Django для статики
Плохо:
# FastAPI НЕ должен отдавать статику
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")
Почему это плохо:
- Блокирует worker процессы
- Медленно (нет кеширования)
- Нет сжатия gzip
- Нет CDN
2. Правильный подход: Nginx + S3/CDN
Архитектура:
Клиент
├─ /api/* → Nginx → FastAPI (Gunicorn)
└─ /static/* → Nginx → disk или S3/CloudFront
3. Nginx конфигурация
nginx.conf:
server {
listen 80;
server_name example.com;
# Кеширование статики
location /static/ {
alias /var/www/static/;
expires 30d; # Браузер кешет на 30 дней
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff";
gzip on; # Сжимаем на лету
gzip_types text/css application/javascript image/svg+xml;
}
# API
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Остальное
location / {
proxy_pass http://127.0.0.1:8000;
}
}
4. Django + Whitenoise (если нужна статика)
Если всё же необходимо отдавать статику из приложения:
pip install whitenoise
settings.py:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # После SecurityMiddleware
# ...
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
# Включаем сжатие и кеширование
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Запуск:
python manage.py collectstatic --no-input
gunicorn config.wsgi
5. FastAPI + Amazon S3 (Best Practice)
Загрузка файлов в S3 (бэкенд):
from fastapi import FastAPI, UploadFile, File
import boto3
from datetime import datetime
app = FastAPI()
s3_client = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY'),
aws_secret_access_key=os.getenv('AWS_SECRET_KEY'),
region_name='us-east-1'
)
S3_BUCKET = os.getenv('S3_BUCKET_NAME')
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
# Генерируем уникальное имя
timestamp = datetime.now().timestamp()
key = f"uploads/{timestamp}_{file.filename}"
# Загружаем в S3
s3_client.upload_fileobj(
file.file,
S3_BUCKET,
key,
ExtraArgs={
'ContentType': file.content_type,
'CacheControl': 'max-age=86400' # 1 день
}
)
# Возвращаем публичный URL
url = f"https://{S3_BUCKET}.s3.amazonaws.com/{key}"
return {"url": url, "filename": file.filename}
6. CloudFront CDN (распределённая доставка)
Terraform конфиг:
resource "aws_cloudfront_distribution" "static" {
origin {
domain_name = "${aws_s3_bucket.static.id}.s3.amazonaws.com"
origin_id = "S3Origin"
}
enabled = true
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
# Кеширование на 30 дней
min_ttl = 0
default_ttl = 86400
max_ttl = 2592000
viewer_protocol_policy = "redirect-to-https"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
7. Docker-compose для production
docker-compose.yml:
version: "3.9"
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./static:/var/www/static:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
app:
build: .
environment:
- DATABASE_URL=postgresql://...
expose:
- 8000
volumes:
- ./app:/app
command: gunicorn -w 4 -b 127.0.0.1:8000 app:app
postgres:
image: postgres:15
environment:
- POSTGRES_DB=app
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
8. Оптимизация статики
Версионирование файлов (cache busting):
# frontend/build/manifest.json
{
"main.js": "main.d4f5c3a2.js",
"main.css": "main.7e8b9f1c.css"
}
В шаблоне:
<script src="/static/{{ manifest['main.js'] }}"></script>
<link rel="stylesheet" href="/static/{{ manifest['main.css'] }}">
Сжатие изображений:
# WebP для современных браузеров
ffmpeg -i image.jpg -vcodec libwebp -quality 80 image.webp
# JPEG optimization
jpegtran -copy none -optimize -progressive image.jpg -o image-optimized.jpg
9. Headers для оптимальной доставки
Nginx конфиг:
location /static/ {
alias /var/www/static/;
# Кеширование
expires 30d;
add_header Cache-Control "public, immutable";
# Сжатие
gzip on;
gzip_types text/css application/javascript image/svg+xml font/woff2;
# Безопасность
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
}
10. Мониторинг и метрики
CloudWatch метрики (AWS):
import boto3
cloudwatch = boto3.client('cloudwatch')
def log_request_count(bucket: str):
cloudwatch.put_metric_data(
Namespace='S3',
MetricData=[
{
'MetricName': 'RequestCount',
'Value': 1,
'Dimensions': [{'Name': 'BucketName', 'Value': bucket}]
}
]
)
Чеклист deployment статики
- Используешь Nginx для раздачи
- Включен gzip compression
- Установлены правильные Cache-Control headers
- Версионирование файлов (cache busting)
- Изображения оптимизированы
- Используется CDN (CloudFront, Cloudflare)
- Настроены SSL/TLS сертификаты
- Мониторинг доставки
- CORS заголовки если нужно
Сравнение подходов
| Метод | Скорость | Масштабируемость | Стоимость |
|---|---|---|---|
| Django/FastAPI | Медленно | Плохо | Низка |
| Nginx + диск | Быстро | Среднее | Низка |
| S3 + CDN | Очень быстро | Отличное | Средняя |
| Cloudflare | Очень быстро | Отличное | Низка |
Вывод
Для production:
- Никогда не отдавай статику через приложение
- Используй Nginx как reverse proxy
- Кешируй на 30+ дней с cache busting
- Храни в S3 для масштабируемости
- Раздавай через CDN для скорости
- Сжимай gzip на лету