Docker-развёртывание Django-проекта: Nginx, Gunicorn, PostgreSQL и Fail2Ban

Пошаговое руководство по настройке для начинающих

Введение

Развёртывание Django-приложения с полным стеком безопасности и автоматизацией. Реальные кейсы из проекта choocha.ru.

1. Архитектура решения

схема взаимодействия контейнеров в докере

 

 

 

 

 

 

 

 

 

 

 

2. Docker Compose: финальная конфигурация

services:
  postgres:
    image: postgres:17-alpine
    container_name: psgr
    restart: always
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./choocha_db_backup.sql:/docker-entrypoint-initdb.d/init.sql # инициализируем базу данных с помощью файла резервной копии
    environment:
      - PGTZ=Europe/Moscow  # Специфичная переменная для Postgres
      - POSTGRES_INITDB_ARGS=--data-checksums  # Для безопасности
    networks:
      - dbnet
  
  app:
    build: ./choocha.ru
    image: choocha.ru
    container_name: choocha.ru-app
    restart: always
    command: "gunicorn -c gunicorn.py choocha.wsgi"
    env_file:
      - .env
    links:
      - "postgres:dbps"
    networks:
      - dbnet
    volumes:
      - ./choocha.ru:/app/www/choocha.ru
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    expose:
      - 8000
    environment:
      - PYTHONUNBUFFERED=1  # Для корректного логирования
      - PYTHONDONTWRITEBYTECODE=1  # Уменьшает образ
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
    depends_on:
      - postgres
  
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: always
    networks:
      - dbnet
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./choocha.ru/static:/app/www/choocha.ru/static
      - ./choocha.ru/media:/app/www/choocha.ru/media:rw
      - ./nginx:/etc/nginx/conf.d
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf/options-ssl-nginx.conf:/etc/letsencrypt/options-ssl-nginx.conf
      - ./certbot/conf/ssl-dhparams.pem:/etc/letsencrypt/ssl-dhparams.pem
      - ./logs/nginx:/var/log/nginx  # Монтируем логи на хост
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    security_opt:
      - no-new-privileges=true
    depends_on:
      - app
  
  certbot:
      image: certbot/certbot
      volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
      command: certonly --webroot --webroot-path=/var/www/certbot --email gogamaster@yandex.ru --agree-tos --no-eff-email --keep-until-expiring -d choocha.ru --noninteractive
      depends_on:
      - nginx

  fail2ban:
    image: crazymax/fail2ban:latest
    container_name: fail2ban
    restart: unless-stopped
    networks:
      - dbnet
    volumes:
      - ./fail2ban:/data
      - ./logs/nginx:/var/log/nginx:ro  # Только чтение
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/log:/var/log:ro  # Мониторим все логи
    cap_add:
      - NET_ADMIN
      - NET_RAW
    environment:
      - TZ=Europe/Moscow
      - F2B_LOG_TARGET=STDOUT
      - F2B_LOG_LEVEL=INFO
      - F2B_DB_PURGE_AGE=30d
      - F2B_IPTABLES_CHAIN=DOCKER-USER  # Явное указание цепочки

networks:
  dbnet:
    driver: bridge
    driver_opts:
      com.docker.network.driver.mtu: 1450
volumes:
  postgres-data:

примечание

  • файл с резервной копией choocha_db_backup.sql является инициализирующим при создании базы данных

  • POSTGRES_INITDB_ARGS=--data-checksums может снизить производительность

  • пример файла .env

    DJANGO_SECRET_KEY="ваш ключ"
    DJANGO_DEBUG=False
    DJANGO_ALLOWED_HOSTS=127.0.0.1, localhost, choocha.ru, www.choocha.ru
    
    # Database settings
    DATABASE_ENGINE=django.db.backends.postgresql
    DATABASE_NAME=имя_базы_данных
    DATABASE_USERNAME=пользователь_базы_данных
    DATABASE_PASSWORD=пароль
    DATABASE_HOST=postgres
    DATABASE_PORT=5432
    
    # Email settings
    
    # Для Яндекса используйте "пароль приложения", а не основной пароль аккаунта
    # Создать: https://id.yandex.ru/security/app-passwords
    EMAIL_HOST=smtp.yandex.ru
    EMAIL_PORT=587
    
    EMAIL_HOST_USER=почтовый_логин
    EMAIL_HOST_PASSWORD=почтовый_пароль
    EMAIL_USE_TLS=True

3. Конфиг для nginx.

# Основной HTTP-сервер (редирект на HTTPS + certbot)
    # Формат логов для fail2ban
    log_format security '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent" '
                      '$request_time';
server {
    listen 80;
    server_name choocha.ru www.choocha.ru;
    access_log /var/log/nginx/choocha.ru_access.log security;
    error_log /var/log/nginx/choocha.ru_error.log warn;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://choocha.ru$request_uri;
    }
}
# Основной HTTPS-сервер
server {
    listen 443 ssl;
    http2 on;
    server_name choocha.ru;
    # SSL-сертификаты (должны монтироваться в контейнер)
    ssl_certificate /etc/letsencrypt/live/choocha.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/choocha.ru/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    # Используем тот же формат логов
    access_log /var/log/nginx/choocha.ru_ssl_access.log security;
    error_log /var/log/nginx/choocha.ru_ssl_error.log warn;
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    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;
    #исключения для яндекс-метрики и гугл-аналитики
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' www.googletagmanager.com mc.yandex.ru yastatic.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: yastatic.net; font-src 'self'; connect-src 'self' mc.yandex.ru; frame-src 'self' mc.yandex.ru; object-src 'none';" always;
    # Статические файлы
    location /static/ {
        alias /app/www/choocha.ru/static/;
        expires 30d;
        access_log off;
    }
    location /media/ {
        alias /app/www/choocha.ru/media/;
        expires 30d;
        access_log off;
    }
    location /favicon.ico {
        alias /app/www/choocha.ru/static/favicon.ico;
        access_log off;
        log_not_found off;
    }
    # Основной прокси-пасс
    location / {
        proxy_pass http://choocha.ru-app:8000;  # Имя сервиса Django из docker-compose
        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_redirect off;
        client_max_body_size 10M;
    }
    # Блокировки безопасности
    location ~ /\. { deny all; access_log off; log_not_found off; }
    location ~* (\.env|wp-|xmlrpc|config\.) { deny all; return 403; }
}

4. Docker-файл проекта

FROM python:3.12-slim
RUN groupadd -r groupdjango && useradd -r -g groupdjango choocha
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN pip install --upgrade pip
WORKDIR /app/www/choocha.ru
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
USER choocha

5. Пример настройки Fail2Ban для Docker

Создаём каталоги. 

mkdir -p fail2ban/{filter.d,jail.d,action.d}

Файлы настроек.

Фильтр

root@cv4702363:/home/site# cat fail2ban/filter.d/nginx-botsearch.conf
[Definition]
failregex =
    # Блокировка по User-Agent
    ^<HOST> -.*".*".*".*"(l9explore|Tsunami|sqlmap|nikto|wget|curl|masscan|zgrab|Go-http-client|python-requests)
    # Сканирование уязвимых файлов
    ^<HOST> -.*(GET|POST).*(/\.env|/\.git/config|/wp-config\.php)
    # Атаки на WordPress
    ^<HOST> -.*(GET|POST).*(wp-includes|wp-admin|wp-login\.php|xmlrpc\.php)
    # Подозрительные PHP-запросы (если PHP не используется)
    ^<HOST> -.*GET.*\.php\?.*(cmd|exec|sh)
    # DNS-атаки
    ^<HOST> -.*(GET|POST).*(resolve|dns-query)\?.*
    # Строгое правило для бинарных атак (только NULL-байты и SMB)
    ^<HOST> -.*"(\\x[0-9A-Fa-f]{2})+.*"
    # Блокировка известных эксплойтов (например, Log4j)
    ^<HOST> -.*"\$\{.*\}.*"
ignoreregex =

Описание

root@cv4702363:/home/site# cat fail2ban/jail.d/nginx-docker.conf
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/choocha.ru_access.log
maxretry = 3
findtime = 1h
bantime = 1d
chain = DOCKER-USER

Действия

root@cv4702363:/home/site# cat fail2ban/action.d/iptables-docker.conf
[Definition]
actionstart = iptables -N f2b-<name>
              iptables -A f2b-<name> -j RETURN
              iptables -I DOCKER-USER 1 -j f2b-<name>
actionstop = iptables -D DOCKER-USER -j f2b-<name>
             iptables -F f2b-<name>
             iptables -X f2b-<name>
actioncheck = iptables -n -L DOCKER-USER | grep -q 'f2b-<name>'
actionban = iptables -I f2b-<name> 1 -s <ip> -j DROP
actionunban = iptables -D f2b-<name> -s <ip> -j DROP

 

root@cv4702363:/home/site# cat fail2ban/jail.local
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 2
findtime = 1h
bantime = 7d
chain = DOCKER-USER
filter = nginx-botsearch.conf

6. Автоматизация бэкапов

Ежедневный бэкап через cron:

скрипт бэкапа в локальное хранилище и на яндекс.диск (с помощью rclone)

root@cv4702363:/home/site# cat postgres_backup.sh
#!/bin/bash
# Параметры
BACKUP_DIR="/home/site/backup"
DB_USER="имя_пользователя"
DB_NAME="база_данных"
CONTAINER_NAME="psgr"
RCLONE_REMOTE="yandex"  # Имя удаленного хранилища в rclone config
RCLONE_PATH="/backups"  # Путь на Яндекс.Диске
# Создаем директорию для бэкапов
mkdir -p "${BACKUP_DIR}"
# Создаем бэкап с timestamp
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_$(date +"%Y-%m-%d_%H-%M-%S").sql.gz"
docker exec -t "${CONTAINER_NAME}" pg_dump -U "${DB_USER}" -d "${DB_NAME}" | gzip > "${BACKUP_FILE}"
# Проверяем успешность создания бэкапа
if [ $? -ne 0 ]; then
    echo "[$(date)] Ошибка при создании бэкапа!" >&2
    exit 1
fi
# Копируем на Яндекс.Диск
rclone copy "${BACKUP_FILE}" "${RCLONE_REMOTE}:${RCLONE_PATH}"
if [ $? -eq 0 ]; then
    echo "[$(date)] Бэкап успешно загружен на Яндекс.Диск: ${BACKUP_FILE}"
else
    echo "[$(date)] Ошибка при загрузке на Яндекс.Диск!" >&2
    # Не выходим с ошибкой, т.к. локальный бэкап всё равно создан
fi
# Удаляем старые бэкапы (сохраняем последние 7)
find "${BACKUP_DIR}" -name "${DB_NAME}_*.sql.gz" -type f -mtime +7 -delete
# Дополнительно: удаляем старые бэкапы на Яндекс.Диске (последние 30 дней)
rclone delete "${RCLONE_REMOTE}:${RCLONE_PATH}" --min-age 30d --include "*.sql.gz"
echo "[$(date)] Резервное копирование завершено. Локальный: ${BACKUP_FILE}, Яндекс.Диск: ${RCLONE_REMOTE}:${RCLONE_PATH}/$(basename ${BACKUP_FILE})" >> /home/site/backup.log

И задание в crontab

0 2 * * * /home/site/postgres_backup.sh >> /home/site/backup.log 2>&1

7. Практические советы

диагностика

# Проверка синтаксиса Nginx
docker exec -it nginx nginx -t
# Проверка правил Fail2Ban
docker exec -it fail2ban fail2ban-client status nginx-botsearch
# Тест бэкапа
zcat backup.sql.gz | docker exec -i psgr psql -U choocha -d choocha_d

В settings.py добавить

CSRF_TRUSTED_ORIGINS = [
    'https://choocha.ru',
    'http://choocha.ru',
    'https://www.choocha.ru',
    'http://www.choocha.ru',
]

возможные ошибки

  • Не загружаются картинки в ckeditor. 

    • Проверка текущих прав (на хосте) 

      ls -ld /home/site/choocha.ru/media/ 
       
    • Если вывод показывает другого владельца (не 999:999): 

      drwxr-xr-x 2 root root 4096 Jun 1 10:00 /home/site/choocha.ru/media/ 
      Исправление прав (выполнять на хосте) 

      sudo chown -R 999:999 /home/site/choocha.ru/media/ 
      sudo chmod -R 775 /home/site/choocha.ru/media/ 
       
    • Проверка внутри контейнера 

      docker exec -it choocha.ru-app ls -ld /app/www/choocha.ru/media 

      drwxrwxr-x 2 choocha groupdjango 4096 Jun 15 10:00 /app/www/choocha.ru/media

     

Категория: Администрирование |автор: fominyh_vv

Опубликовано: 05-05-2025 13:16

Комментарии (1)

Аватар fominyh_vv fominyh_vv написал 07-05-2025 16:46 (изменено: 16-05-2025 14:12)

Тестовый комментарий

Чтобы оставить комментарий, пожалуйста войдите или зарегистрируйтесь.