Давайте писать удобное локальное окружение…

от автора

Всем привет, меня зовут Аббакумов Валерий.

Я Python разработчик, в основном занимаюсь бэкэндом веб приложений и каждый раз когда дело доходит до разворачивания нового проекта по моей щеке начинает течь слеза.

Думаю, никто не будет спорить с тем, что локальное разворачивание нового проекта может вызвать неограниченное количество проблем. В данной статье я хочу представить выстраданную годами структуру проекта и организацию его окружения, которые помогают избежать большей части проблем, связанных с локальным разворачиванием проекта.

Пример будет представлен для Django проекта и PDM в качестве менеджера зависимостей, но концептуально должен подходить для любого проекта на любом языке и с любым набором сервисов. Так же у меня есть наработки для докерфайла с использованием Poetry, если это кому-то интересно, могу добавить информацию и для этого менеджера зависимостей.

Что будет в этой статье?

  1. Структура проекта. Как разложить все по полкам

  2. Управление переменными окружения. Как, где и почему лежат переменные

  3. Создание локальных файлов. Основной скрипт подготовки окружения для работы

  4. Docker. Описание docker-compose и Dockerfile файлов

  5. Запуск проекта. Основные команды

  6. Прочие файлы. Примеры некоторых файлов проекта, которые зачастую проблемно составить быстро и сразу

  7. Заключение. Краткий вывод и пожелания

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

Очень часто можно наблюдать как корень проекта представляется своего рода свалкой без какой либо структуры, что в свою очередь значительно усложняет понимание того, как проект организован и как его запускать. Очень хорошо если присутствует хотя бы детально описанный README.md и еще лучше, если он постоянно актуализируется, но зачастую приходится спрашивать у своих коллег что вообще происходит в из репозиториях.

Очень важно группировать компоненты по их назначению, это автоматически формирует интуитивно понятную структуру. За 5 лет разработки я пришел к следующей минимальной структуре приложения, которая не перегружает корень проекта, является интуитивно понятной и достаточно гибкой для использования и расширения.

Важно отметить, что подобного рода структура наиболее эффективна именно для разработки на локальной машине, конечно же, для реального крупного приложения, работающего в production среде нужно куда больше. Масштабировать проще и лучше с k8s, про хранение секретов должна быть как минимум отдельная статья, а про CI-CD целый сборник статей. Но в любом случае, все описанное ниже никак не исключает хороший системный подход к разворачиванию и доставке кода на ваши сервера, но в огромной степени упрощает жизнь разработчиков, которые работают на своих тачках и вынуждены поднимать всю инфраструктуру локально.

├── .local_files # Локальные файлы проекта под gitignore │   ├── asgi # Локальные файлы основного приложения │   │   ├── log # Логи │   │   │   └── ... │   │   ├── media # Медиа файлы │   │   │   └── ... │   │   ├── static # Статика  │   │   │   └── ... │   │   ├── tmp # Временные файлы контейнера приложения │   │   │   └── ... │   │   └── ... │   ├── celery # Локальные файлы celery │   │   ├── log # Логи │   │   │   └── ... │   │   └── ... │   ├── nginx # Локальные файлы nginx │   │   ├── certs # Сертификаты │   │   │   └── ... │   │   ├── conf # Конфигурация nginx │   │   │   └── ... │   │   ├── log # Логи  │   │   │   └── ... │   │   └── ... │   ├── postgres # Локальные файлы postgres │   │   ├── backup # Директория с бэкапами │   │   │   └── ... │   │   ├── data # Данные бд (при удалении и перезапуске контейнера инициализируется новая бд) │   │   │   └── ... │   │   ├── log # Логи │   │   │   └── ... │   │   └── ... │   ├── redis # Локальные файлы redis │   │   ├── data # Данные redis │   │   │   └── ... │   │   ├── log # Логи │   │   │   └── ... │   │   └── ... │   └── ... ├── docker # Директория для инструментов, и конфигурации docker │   ├── composes # Опциональные расширения docker-compose файла │   │   ├── docker-compose.dev.yml # DEV сборка приложения  │   │   ├── docker-compose.prod.yml # PROD сборка приложения │   │   ├── docker-compose.docs.yml # Собирать документацию │   │   ├── docker-compose.build-asgi-locally.yml # Локальная сборка приложения  │   │   ├── docker-compose.build-docs-locally.yml # Локальная сборка документации │   │   ├── docker-compose.postgres.yml # Запускать  postgres в docker  │   │   ├── docker-compose.redis.yml # Запускать  redis в docker  │   │   ├── docker-compose.override.yml # Переопределение под gitignore │   │   └── ... │   ├── confs # Конфиги, которые прокидываются в volumes или dockerfiles │   │   ├── nginx.dev.conf │   │   ├── nginx.prod.conf │   │   └── ... │   ├── dockerfiles # Специфические докерфайлы │   │   ├── asgi.dockerfile # Докерфайл приложения │   │   ├── postgres.dockerfile # Докерфайл postgress │   │   └── ... │   ├── inits # Скрипты, выполняемые при первом запуске контейнера или сборке │   │   ├── postgresql.init.sql │   │   └── ... │   └── ... ├── docs # Директория для документации, я использую mkdocs (Очень рекомендую, помогает делать красивые end to end странички для онбординга сотрудников в проект) │   ├── index.md │   └── ... ├── pipelines # пайплайны GitLab   │   └── ... ├── scripts # Скрипты для быстрого доступа к частоиспользуемым командам │   ├── bash.sh │   ├── dbshell.sh │   ├── init.sh │   ├── manage.sh │   ├── shell.sh │   └── ... ├── src # Код самого приложения │   ├── apps # Приложения с бизнес-логикой │   │   ├── __init__.py │   │   └── ... │   ├── config # Конфигурация django приложения │   │   ├── __init__.py │   │   ├── asgi.py │   │   ├── celery.py │   │   ├── urls.py │   │   └── ... │   ├── contrib # Модули не привязанные к бизнес-логике │   │   ├── __init__.py │   │   └── ... │   ├── plugins # Опциональные приложения с бизнес-логикой │   │   ├── __init__.py │   │   └── ... │   ├── settings # Директория с настройками приложения │   │   ├── __init__.py │   │   ├── apps_settings.py # Настройки бизнес-логики │   │   ├── base_settings.py # Настройки путей, режима работы, url │   │   ├── deps_settings.py # Креды сторонних приложений │   │   ├── django_settings.py # Специфические для django настройки │   │   ├── local_settings.py # Локальные настройки под gitignore в большинстве случаев здесь импортятся все остальные настройки кроме  │   │   ├── local_settings.sample.py # Пример локальных настроек │   │   ├── test_settings.py # Настройки для тестов │   │   └── ... │   ├── __init__.py # Тут я указываю версию проекта __version__ = "0.0.1" │   ├── manage.py # Не забудте указать os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.local_settings") │   └── ... ├── .dockerignore # Файлы, которые игнорирует docker ├── .env # Автоматически генерируемый файл переменных окружения (под gitignore) ├── .env.base # Базовые переменные окружения ├── .env.local # Локальные переменные окружения (под gitignore) ├── .env.local.sample # Пример локальных переменных окружения ├── .gitignore # Файлы, которые игнорирует git ├── .gitlab-ci.yml # Конфигурация пайплайнов gitlab ├── .pre-commit-config.yaml # Конфигурация прекоммита ├── .prepare.sh # Скрипт подготовки окружения (выполняетс в compose) ├── compose # Утилита для вызова команд docker compose в нашем окружении ├── docker-compose.yml # Основной docker-compose файл ├── mkdocs.yml # Конфигурация докментации ├── pyproject.toml # Файл метаданных проекта ├── pdm.lock # Лок PDM (Возможно вы используете poetry или что-то еще) ├── CHANGELOG.md # Файл истории изменения версий ├── README.md # Файл с описанием проекта и взаимодействия с ним └── ...
  • local_files # Локальные файлы проекта

    Локальные файлы проекта очень полезно хранить в отдельной директории на хосте под gitignore. Всегда можно получить доступ к старым логам, бэкапам, временным файлам, сертификатам, конфигурациям приложений и так далее, можно добавлять и удалять файлы сразу в контейнере оставаясь на хосте и не вызывая docker exec команды.
    Кстати, если вы очень хотите видеть содержание директорий типа data для postgres, redis и прочих сервисов, которые вы добавите сами и смонтируете, не забудьте пошарить права для хоста, иначе без sudo доступ к каталогам вы не получите, а следовательно ваша IDE будет отображать пустые каталоги.

  • docker # Директория для инструментов, и конфигурации docker

    Все что касается организации docker следует также выносить в отдельную директорию, более того файлы в этой директории можно группировать по назначению, а именно composes, dockerfiles, initsи confs , конечно это опционально и некоторые группы можно как убрать так и добавить, но не стоит перегружать директории разнородными файлами это усложняет понимание общей структуры

  • docs # Директория для документации, я использую mkdocs

    При установке mkdocstrings можно настроить автодокументирование вашей кодовой базы, ниже будет приведен пример настройки документации

  • pipelines # Пайплайны GitLab.

    Позволю себе не описывать настройку и всю боль, которую я получил от опыта настройки пайплайнов в GitLab, думаю ,это выходит за рамки данной статьи

  • scripts # Скрипты для быстрого доступа к часто используемым командам.

    Сюда лучше всего складывать все команды, которые так или иначе будут вызываться в командной строке в корне проекта, например вызов shell_plus, dbshell, manage.py, дамп бд и так далее. Для того чтобы не перегружать корень проекта в репозитории скрипты сгруппированы в отдельной директории, а локально для удобства на каждый скрипт в директории создается символическая ссылка в корне проекта

  • src # Код самого приложения

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

  • .env* # Файлы с переменными окружения

    Вы можете заметить, что файлы с данного типа не выделены в отдельную группу каталогом, а лежат в каталоге. На это есть 2 причины: во-первых, вынося переменные окружения в подкаталог теряется интуитивная ясность того, что скорее всего нужно что-то заполнить, во-вторых .env файл лежит там же, где и основной docker-compose.yml файл, что позволяет не устраивать танцы с бубнами

Управление переменными окружения

В большинстве проектов в репозитории либо вообще нет никаких переменных окружения либо есть какой-то пример, который нужно значительно редактировать либо пример в котором не ясно, что нужно редактировать.

Зачастую мы имеем определенный набор переменных окружения, которые с большой долей вероятности не нужно будет изменять и которые в свою очередь не являются кредами. Данные переменные имеет смысл выносить в репозиторий отдельным файлом .env.base

# Основные настройки # Префикс docker контейнеров COMPOSE_PROJECT_NAME=<you_project> # Файл настроек django DJANGO_SETTINGS_MODULE=settings.local_settings # Использовать debug режим DEBUG=False # Запущено в prod среде PRODUCTION=False # Количество запускаемых рабочих WORKERS_COUNT=4  # Идентификация юзера USER_NAME="$(echo $USER)" USER_ID="$(id -u)" GROUP_ID="$(id -g)"  # Образ приложения # Путь до docker registry DOCKER_REGISTRY=<you_project_docker_registry> # Название образа DOCKER_IMAGE=<you_project_docker_image> # Тег образа TAG=<you_project_docker_tag>  # Директории # Корень проекта PROJECT_DIR=$(cd $(dirname $(readlink -e $0)) && pwd) # Директория с кодом приложения SOURCE_DIR=${PROJECT_DIR}/src # Директория docker DOCKER_DIR=${PROJECT_DIR}/docker # Директория с docker-compose файлами DOCKER_COMPOSES_DIR=${DOCKER_DIR}/composes # Директория с конфигурациями для различных серфисов DOCKER_CONFS_DIR=${DOCKER_DIR}/confs # Директория с Dockerfile DOCKER_DOCKERFILES_DIR=${DOCKER_DIR}/dockerfiles # Директория со скриптами инициализации DOCKER_INITS_DIR=${DOCKER_DIR}/inits # Директория с локальными файлами LOCAL_FILES_DIR=${PROJECT_DIR}/.local_files  # Очень важно разделить ворты в сети докер и порты в сети хоста  # чтобы была возможность менять порты с любой стороны по мере необходимости # Порты на хосте ASGI_PORT_EXTERNAL=8000 POSTGRES_PORT_EXTERNAL=5432 REDIS_PORT_EXTERNAL=6379 NGINX_PORT_EXTERNAL=80 FLOWER_PORT_EXTERNAL=5555 DOCS_EXTERNAL_PORT=8001  # Порты в сети докера ASGI_PORT_INTERNAL=8000 POSTGRES_PORT_INTERNAL=5432 REDIS_PORT_INTERNAL=6379 NGINX_PORT_INTERNAL=80 FLOWER_PORT_INTERNAL=5555 DOCS_PORT_INTERNAL=8001  # Настройки docker-compose # Основной файл docker-compose COMPOSE_FILE="${PROJECT_DIR}/docker-compose.yml" # Запускать redis в докере RUN_WITH_REDIS=True # Запускать postgres в докере RUN_WITH_POSTGRES=True # Добавить docker-compose.override.yml RUN_WITH_OVERRIDE_DOCKER_COMPOSE=True # Собирать образ локаьно RUN_WITH_BUILD_LOCALLY=False # Запускать сервер с документацией RUN_WITH_DOCS=False  # Настройки почты MAIL_PORT=465 MAIL_SERVER=smtp.gmail.com # Использовать S3 USE_S3_STORAGE=False # Ключ для генерации токенов SECRET_KEY=example_key

Помимо этого всегда есть необходимость определить или переопределить какие-то переменный локально. Для этого используем .env.local, который генерируется на основании .env.local.sample

# Основные настройки DEBUG=True  # Настройки админ юзера, обычно я их тяну в manage.py команде при инициализации  # проекта для автоматического создания юзера ADMIN_USERNAME=admin ADMIN_PASSWORD=password_example  # Собирать образ приложения локально (альтернатива стягиванию из регистри) RUN_WITH_BUILD_LOCALLY=True # Запустить сервер с документацией RUN_WITH_DOCS=True  # Настройки postgres POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_USER=postgres POSTGRES_PASSWORD=password_example POSTGRES_DB=postgres POSTGRES_DEBUG=False  # Настройки redis REDIS_HOST=redis REDIS_PORT=6379  # Настройки почты MAIL_USERNAME= MAIL_PASSWORD= MAIL_FROM=${MAIL_USERNAME} MAIL_PORT=465 MAIL_SERVER=smtp.gmail.com  # Настройки sentry SENTRY_DSN=  # Настройки S3 USE_S3_STORAGE=False AWS_STORAGE_BUCKET_NAME= AWS_S3_ACCESS_KEY_ID= AWS_S3_SECRET_ACCESS_KEY= AWS_S3_ENDPOINT_URL=

Далее переменные, объявленные в этих 2 файлах будут собраны в итоговый .env файл для удобства использования

Создание локальных файлов

Приведенный ниже скрипт:

  • создает всю структуру локальных файлов проекта

  • создает локальные настройки, которые также находятся под gitignore:

    • для переменных окружения .env.local

    • для запуска контейнеров docker-compose.override.yml

    • для приложения local_settings.py

  • создает символические ссылки на часто используемые скрипты

  • создает общий .env файл, который используется для запуска приложения

  • записывает в переменные окружения конфигурацию запуска docker compose

#!/bin/bash  # Функция для вывода переменных, определенных во время исполнения скрипта  print_script_variables() {     set | sort | comm -13 .env.tmp - }  # Записываем переменные окружения и функции оболочки во временный файл set | sort > .env.tmp  # Импортируем базовые переменные окружения source ".env.base"  # Создаем локальные файлы их их шаблонов, если они еще не созданы cp -n ${PROJECT_DIR}/.env.local.sample ${PROJECT_DIR}/.env.local cp -n ${SOURCE_DIR}/settings/local_settings.sample.py ${SOURCE_DIR}/settings/local_settings.py  # Импортируем локальные переменные окружения if [[ -f .env.local ]]; then   source ".env.local" fi  # Создаем директорию с локальными фойлами mkdir -p "${LOCAL_FILES_DIR}"  # Создаем символические ссылки на скрипты (сюда можно добавить создание и загрузку дампа бд) ln -sf "${PROJECT_DIR}/scripts/bash.sh" "${PROJECT_DIR}/bash" ln -sf "${PROJECT_DIR}/scripts/dbshell.sh" "${PROJECT_DIR}/dbshell" ln -sf "${PROJECT_DIR}/scripts/manage.sh" "${PROJECT_DIR}/manage" ln -sf "${PROJECT_DIR}/scripts/shell.sh" "${PROJECT_DIR}/shell" ln -sf "${PROJECT_DIR}/scripts/init.sh" "${PROJECT_DIR}/init"  # Создание локальных файлов для postgres: mkdir -p "${LOCAL_FILES_DIR}/postgres" mkdir -p "${LOCAL_FILES_DIR}/postgres/log" mkdir -p "${LOCAL_FILES_DIR}/postgres/data" mkdir -p "${LOCAL_FILES_DIR}/postgres/backup" # Создание локальных файлов для redis: mkdir -p "${LOCAL_FILES_DIR}/redis" mkdir -p "${LOCAL_FILES_DIR}/redis/log" mkdir -p "${LOCAL_FILES_DIR}/redis/data" # Создание локальных файлов для asgi: mkdir -p "${LOCAL_FILES_DIR}/asgi" mkdir -p "${LOCAL_FILES_DIR}/asgi/log" mkdir -p "${LOCAL_FILES_DIR}/asgi/tmp" mkdir -p "${LOCAL_FILES_DIR}/asgi/media" # Создание локальных файлов для centry: mkdir -p "${LOCAL_FILES_DIR}/celery" mkdir -p "${LOCAL_FILES_DIR}/celery/log" # Создание локальных файлов для nginx: mkdir -p "${LOCAL_FILES_DIR}/nginx" mkdir -p "${LOCAL_FILES_DIR}/nginx/conf" mkdir -p "${LOCAL_FILES_DIR}/nginx/log" mkdir -p "${LOCAL_FILES_DIR}/nginx/certs"  # Создания docker-compose.override.yml (под gitignore) touch "${DOCKER_COMPOSES_DIR}/docker-compose.override.yml"  # Создание конфигов nginx из их шаблонов export NGINX_PORT_INTERNAL=${NGINX_PORT_INTERNAL} export ASGI_PORT_INTERNAL=${ASGI_PORT_INTERNAL} export DOCS_PORT_INTERNAL=${DOCS_PORT_INTERNAL} export HOST_NAME=${HOST_NAME} # Следующие команды заполняют шаблон значениями переменных окружения envsubst '${NGINX_PORT_INTERNAL},${ASGI_PORT_INTERNAL},${DOCS_PORT_INTERNAL},${HOST_NAME}' \          < "${DOCKER_CONFS_DIR}/nginx.dev.conf" > \          "${LOCAL_FILES_DIR}/nginx/conf/nginx.dev.conf" envsubst '${NGINX_PORT_INTERNAL},${ASGI_PORT_INTERNAL},${DOCS_PORT_INTERNAL},${HOST_NAME}' \          < "${DOCKER_CONFS_DIR}/nginx.prod.conf" > \          "${LOCAL_FILES_DIR}/nginx/conf/nginx.prod.conf"  # Дополнение docker-compose.yaml в зависимости от значений переменных окружения if [[ "${RUN_WITH_REDIS}" = "True" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.redis.yml" fi if [[ "${RUN_WITH_POSTGRES}" = "True" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.postgres.yml" fi if [[ "${RUN_WITH_OVERRIDE_DOCKER_COMPOSE}" = "True" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.override.yml" fi if [[ "${RUN_WITH_DOCS}" = "True" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.docs.yml" fi if [[ "${RUN_WITH_BUILD_LOCALLY}" = "True" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.build-asgi-locally.yml"     if [[ "${RUN_WITH_DOCS}" = "True" ]]; then       COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.build-docs-locally.yml"     fi fi if [[ "${PRODUCTION}" = "False" ]]; then     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.dev.yml" else     COMPOSE_FILE="${COMPOSE_FILE}:${DOCKER_COMPOSES_DIR}/docker-compose.prod.yml" fi  # Записываем в файл переменные окружения, объявленные в процессе выполнения текущего скрипта EXCLUDE_RULES="^\(SHLVL=\|LINES=\|COLUMNS=\|BASH_SOURCE=\|BASH_LINENO=\|BASH_ARGV=\|BASH_ARGC=\|EXCLUDE_RULES=\|_=\|FUNCNAME=\|PIPESTATUS=\)" print_script_variables | grep -v $EXCLUDE_RULES > ${PROJECT_DIR}/.env  # Удаляем временный файл rm -f .env.tmp

Данный скрипт вызывается при каждом запуске compose с помощью которого мы в взаимодействуем с приложением, подробнее будет описано ниже

Примечание:
Начало и конец скрипта, связанные с вычленением объявленных переменных конечно же является ужаснейшим костылем, но никакого более элегантного решения я самостоятельно найти не смог. Буду очень рад, если кто-то предложит красивую альтернативу, не требующую установки сторонних приложений, не присутствующих в стандартных оболочках

Docker

Мы будем использовать набор из нескольких docker-compose*.yaml файлов а также много стадийный Dockerfile

Основные рекомендации по docker-compose файлам:

  • Выделяйте общие части в переменные

  • Используйте абсолютные пути в volumes во избежание конфликтов с другими проектами

  • Старайтесь по-максимуму использовать переменные окружения и по-минимуму хардкод

  • Не забывайте указывать зависимости и помните, что иногда вам придется добавлять кастомный скрипт для проверки доступности приложения в контейнере

  • Помните, что вы можете тонко настроить выделение ресурсов, сети, маски подсетей и так далее. А еще существует docker swarm если вы ну очень не хотите использовать k8s, в любом случае, читайте документацию

Основной файлdocker-compose.yaml
Здесь мы описываем сервисы, которые не являются опциональными либо те, что по логическим соображениям должны находиться вместе, тут все на ваше усмотрение, так как всегда есть возможность добавить любой сервис в отдельном файле

# Выделяем основную часть настроек приложения в переменную, чтобы не повторяться x-asgi-base: &asgi-base   # Тянем образ из нашего docker-registry   image: ${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${TAG}   restart: on-failure   user: ${USER_ID}:${GROUP_ID}   # Прокидываем переменные окружения   env_file:     - .env   # Все наши сервисы для удобства будут в одной сети   networks:     - app-network  services:   # Сервис основного приложения   asgi:     # Вставляем часть документа, описанную выше     <<: *asgi-base     hostname: asgi     container_name: ${COMPOSE_PROJECT_NAME}-asgi     # Прокидываем порты. Делаем разделение настройки внешних и внутренних портов для того,     # чтобы можно было запускать несколько подобных приложений без необходимости изменять порты внутри докера     ports:         - "${ASGI_PORT_EXTERNAL}:${ASGI_PORT_INTERNAL}"     # Важно маунтить volumes по абсолютным путям так как с относительными могут возникнуть конфликты в нейминге     volumes:       - ${LOCAL_FILES_DIR}/asgi/log/:/var/log/asgi/       - ${LOCAL_FILES_DIR}/asgi/tmp/:/tmp/       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media    # Сервис периодических задач   celery-beat:     # Аналогично asgi     <<: *asgi-base     hostname: celery-beat     container_name: ${COMPOSE_PROJECT_NAME}-celery-beat     command: bash -c '       celery -A config beat -l info --logfile=/var/log/celery/celery_beat.log -s celerybeat-schedule'     volumes:       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/celery/       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/asgi/       - ${LOCAL_FILES_DIR}/asgi/tmp/:/tmp/       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media    # Сервис дефолтной очереди задач (конечно же можно /нужно увеличить количество очередей)   celery-default:     # Аналогично asgi     <<: *asgi-base     hostname: celery-default     container_name: ${COMPOSE_PROJECT_NAME}-celery-default     command: bash -c '       celery -A config worker -l info --concurrency=5 --logfile=/var/log/celery/celery_default.log -Q default'     volumes:       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/celery/       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/asgi/       - ${LOCAL_FILES_DIR}/asgi/tmp/:/tmp/       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media    # Сервис мониторинга celery задач   flower:     # Аналогично asgi     <<: *asgi-base     hostname: flower     container_name: ${COMPOSE_PROJECT_NAME}-flower     command: bash -c 'celery -A config flower --address=0.0.0.0 --port=${FLOWER_PORT_INTERNAL}'     ports:       - "${FLOWER_PORT_EXTERNAL}:${FLOWER_PORT_INTERNAL}"     volumes:       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/celery/       - ${LOCAL_FILES_DIR}/celery/log/:/var/log/asgi/       - ${LOCAL_FILES_DIR}/asgi/tmp/:/tmp/       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media    # HTTP-сервис   nginx:     image: nginx:1.25.3     hostname: nginx     container_name: ${COMPOSE_PROJECT_NAME}-nginx     restart: on-failure     ports:       - "${NGINX_PORT_EXTERNAL}:${NGINX_PORT_INTERNAL}"     volumes:       - ${LOCAL_FILES_DIR}/nginx/log/:/var/log/nginx/       - ${LOCAL_FILES_DIR}/asgi/tmp/:/tmp/       # Важно не забыть прокинуть статику и медиафайлы основного приложения       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media     depends_on:       - asgi     networks:       - app-network  networks:   app-network:     name: ${COMPOSE_PROJECT_NAME} 

Файл для разработки docker/composes/docker-compose.dev.yaml
Тут мы просто расширяем наш основной docker-compose.yaml необходимыми для среды разработки элементами, а именно маунтим volumes для возможности установки библиотек через менеджер зависимостей, а также маунтим код проекта для поддержки работы в режиме hot-reload

services:   # Прописываем команду для запуска dev сервера   asgi:     command: bash -c 'python manage.py runserver 0.0.0.0:${ASGI_PORT_INTERNAL}'     # Прокидываем код, pdm и pyproject.toml для возможности установки библиотек     volumes:       - ${SOURCE_DIR}/:/project/src/       - ${PROJECT_DIR}/pdm.lock:/project/pdm.lock       - ${PROJECT_DIR}/pyproject.toml:/project/pyproject.toml    celery-beat:     volumes:       - ${SOURCE_DIR}/:/project/src/   celery-default:     volumes:       - ${SOURCE_DIR}/:/project/src/   flower:     volumes:       - ${SOURCE_DIR}/:/project/src/    # Прокидыываем dev конфигурацию nginx   nginx:     volumes:       - ${LOCAL_FILES_DIR}/nginx/conf/nginx.dev.conf:/etc/nginx/conf.d/default.conf 

Файл для прод среды docker/composes/docker-compose.prod.yaml
Понимаю, что «Для прод среды» — сильно громко сказано, но суть в том, что мы уже не хотим синхронизировать код на хосте и в контейнере и хотим иметь более чем одного запущенного воркера. В реальном проекте, конечно такой конфиг может быть в prod окружении, но я бы все-таки порекомендовал обратиться к DevOps инженерам.

services:   # Прописываем команду для запуска gunicorn на uvicorn воркерах   asgi:     command: bash -c 'python -m gunicorn config.asgi:application --bind 0.0.0.0:8000 --worker-class uvicorn.workers.UvicornWorker --timeout 180 --workers $WORKERS_COUNT --log-level debug --access-logfile - --error-logfile - --pid /tmp/gunicorn.pid'    # Прокидыываем prod конфигурацию nginx   nginx:     volumes:       - ${LOCAL_FILES_DIR}/nginx/conf/nginx.prod.conf:/etc/nginx/conf.d/default.conf 

Файл для запуска сервера документации docker/composes/docker-compose.docs.yml
По-хорошему этот сервис должен запускаться только во время локальной разработки. Mkdocs имеет возможность собрать dist проекта, что я рекомендую делать в пайплайнах и уже раздавать статику на ваших серверах

services:   # Сервис документации   docs:     image: ${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${TAG}     container_name: ${COMPOSE_PROJECT_NAME}-docs     env_file:       - ${PROJECT_DIR}/.env     environment:       - LOCAL_PYTEST=1       - ENVIRONMENT_TYPE=docker     volumes:       - ${PROJECT_DIR}:/src       - ${LOCAL_FILES_DIR}/asgi/static:/mnt/resource/static       - ${LOCAL_FILES_DIR}/asgi/media:/mnt/resource/media       - ${LOCAL_FILES_DIR}/asgi/tmp:/tmp     # Запускаем веб сервер документации     command: mkdocs serve --dev-addr 0.0.0.0:${DOCS_PORT_INTERNAL}     ports:       - "${DOCS_EXTERNAL_PORT}:${DOCS_PORT_INTERNAL}"     networks:       - app-network 

Файл для redis docker/composes/docker-compose.redis.yml
Наверное самая простая часть во всем нашем окружении, честно говоря, множество сервисов можно описать примерно так же и этого будет достаточно для большей части задач, но надеюсь, что в данной статье описано достаточно кейсов для того, чтобы вы не ограничивались минимальными возможностями настройки вашего окружения

services:   redis:     image: redis:7.4.0     container_name: ${COMPOSE_PROJECT_NAME}-redis     networks:       - app-network     ports:       - "${REDIS_PORT_EXTERNAL}:${REDIS_PORT_INTERNAL}"    # Добавляем redis в качестве зависимости   asgi:     depends_on:       - redis   celery-default:     depends_on:       - redis   celery-beat:     depends_on:       - redis   flower:     depends_on:       - redis 

Файл для postgres docker/composes/docker-compose.postgres.yml
Для postgres использую кастомный Dockerfile так как по непонятным мне причинам в дефолтном образе отсутствует утилита для создания дампа

services:   postgres:     # Собираем образ     build:       context: ${PROJECT_DIR}       dockerfile: ${DOCKER_DOCKERFILES_DIR}/postgres.dockerfile     container_name: ${COMPOSE_PROJECT_NAME}-postgres     command: ["postgres", "-c", "log_statement=all"]     shm_size: 1g     # Прокидываем скрипт инициализации и локлаьные файлы     volumes:       - ${DOCKER_INITS_DIR}/postgresql.init.sql:/docker-entrypoint-initdb.d/init.sql       - ${LOCAL_FILES_DIR}/postgres/data/:/var/lib/postgresql/data/       - ${LOCAL_FILES_DIR}/postgres/log/:/var/log/postgresql/       - ${LOCAL_FILES_DIR}/postgres/backup/:/backup/     # Прокидываем переменные для конфигурации базы     environment:       - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}       - POSTGRES_USER=${POSTGRES_USER}       - POSTGRES_DB=${POSTGRES_DB}     ports:       - "${POSTGRES_PORT_EXTERNAL}:${POSTGRES_PORT_INTERNAL}"     networks:       - app-network    # Добавляем postgres в качестве зависимости   asgi:     depends_on:       - postgres     links:       - postgres:postgres   celery-default:     depends_on:       - postgres     links:       - postgres:postgres   celery-beat:     depends_on:       - postgres     links:       - postgres:postgres   flower:     depends_on:       - postgres     links:       - postgres:postgres 

Файл для локальной сборки приложения docker/composes/docker-compose.build-asgi-locally.yml
Данный файл необходим, когда вы не можете / не хотите / вынуждены собрать образ приложения локально, например вы добавляете библиотеку

x-asgi-local-build: &asgi-local-build   image: app:latest   # Собираем образ из нужного слоя докерфайла   build:     context: ${PROJECT_DIR}     dockerfile: ${DOCKER_DOCKERFILES_DIR}/asgi.dockerfile     target: development     args:       - USER_NAME=${USER_NAME}       - USER_ID=${USER_ID}       - GROUP_ID=${GROUP_ID}  # Обновляем сборку образа в сервисах services:   asgi:     <<: *asgi-local-build   celery-beat:     <<: *asgi-local-build   celery-default:     <<: *asgi-local-build   flower:     <<: *asgi-local-build 

Файл для локальной сборки документации docker/composes/docker-compose.build-docs-locally.yml
Этот файл является своего рода аппендиксом, так как я не придумал, как иначе разрешить кейс, когда, мне нужно локально собрать образ приложения, но не нужно запускать сервис документации

services:   docs:     image: app:latest     build:       context: ${PROJECT_DIR}       dockerfile: ${DOCKER_DOCKERFILES_DIR}/asgi.dockerfile       target: development       args:         - USER_NAME=${USER_NAME}         - USER_ID=${USER_ID}         - GROUP_ID=${GROUP_ID} 

Файл сборки postgresdocker/dockerfiles/postgres.dockerfile

FROM postgres:16.4-bullseye # Устанавливаем утилиту для возможности создания дампа RUN apt-get -y update RUN apt-get -y install postgresql-16-repack

Файл сборки приложенияdocker/dockerfiles/postgres.dockerfile
На самом деле ниже описана квинтэссенция моих познаний и обрывков организации Dockerfile которые я успел увидеть за всю мою практику программирования. Еще 2 редакции этого файла назад я думал, что в нем уже нечего исправлять, но как вы, наверное, догадались, с тех пор файл претерпел значительные изменения, потому, буду безмерно рад любым советам по улучшению данного файла

# ---STAGE_1--- # Описываем стадию базового  python  образа # Указываем платформу сборки через переменную окружения FROM --platform=$BUILDPLATFORM python:3.12.6-slim AS python-base  # Определяем аргументы, которые можно будет передать как аргументы сборки ARG USER_ID=1000 ARG GROUP_ID=1000 ARG USER_NAME="user" ARG GROUP_NAME="user"  # Описываем базовое окружение для python ENV PYTHONUNBUFFERED=1 \     PYTHONDONTWRITEBYTECODE=1 \     PIP_NO_CACHE_DIR=off \     PIP_DISABLE_PIP_VERSION_CHECK=on \     PIP_DEFAULT_TIMEOUT=10 \     PDM_REQUEST_TIMEOUT=10 \     PDM_VERSION=2.19.1 \     PDM_CACHE_DIR="/opt/.cache/pdm" \     PDM_LOG_DIR="/opt/.local/pdm" \     PDM_CHECK_UPDATE=false \     PDM_HOME="/opt/pdm" \     PROJECT_PATH="/project" \     APPLICATION_PATH="/project/src" \     VENV_PATH="/project/.venv" \     TZ="Europe/Moscow" \     LANG="en_US.UTF-8" \     LC_ALL="en_US.UTF-8"  # Добавляем PDM_HOME в PATH ENV PATH="$PDM_HOME/bin:$VENV_PATH/bin:$PATH"  # ---STAGE-2--- # Описываем стадию сборки pdm FROM python-base AS base-pdm-builder  # Устанавливаем все, необходимые зависимости для сборки RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \     --mount=type=cache,target=/var/lib/apt,sharing=locked \     DEBCONF_NONINTERACTIVE_SEEN=true DEBIAN_FRONTEND=noninteractive apt-get update  \     &&  apt-get install -y -qq --no-install-recommends  \         build-essential \         ca-certificates \         gcc \         git \         curl \         pkg-config \         libcairo2-dev \         libjpeg-dev \         libgif-dev \         python3-venv \     && curl -sSL https://pdm-project.org/install-pdm.py | python3 - \     # отчищаем кэш для уменьшения размера образа     && apt-get clean \     && rm -rf /tmp/* /var/lib/apt/lists/*  # Указвыаем рабочую директорию WORKDIR $PROJECT_PATH  # Прокидываем файлы, необходимые для корректной работы pdm COPY --link ./pdm.lock ./pyproject.toml ./README.md ./ COPY --link ./src/__init__.py $APPLICATION_PATH/__init__.py  # ---STAGE-3--- # Стадия сборки зависимостей приложения для dev среды FROM base-pdm-builder AS development-pdm-builder  RUN pdm install --check --dev  # ---STAGE-4--- # Стадия сборки зависимостей приложения для test среды FROM base-pdm-builder AS test-pdm-builder  RUN pdm install --check -G test --no-self  # ---STAGE-5--- # Стадия сборки зависимостей приложения для prod среды FROM base-pdm-builder AS production-pdm-builder  RUN pdm install --check --production --no-self  # ---STAGE-6--- # Базовая стадия сборки зависимостей для python образа FROM python-base AS base-project-builder  ARG USER_ID ARG GROUP_ID  WORKDIR $APPLICATION_PATH  # Устанавливаем все, необходимые зависимости для работы приложения RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \     --mount=type=cache,target=/var/lib/apt,sharing=locked \     DEBCONF_NONINTERACTIVE_SEEN=true DEBIAN_FRONTEND=noninteractive apt-get update  \     &&  apt-get install -y -qq --no-install-recommends  \         locales \         libmagic1 \         pkg-config \         libcairo2-dev \         libjpeg-dev \         libgif-dev \     # Настраиваем язык     && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \     && dpkg-reconfigure --frontend=noninteractive locales \     && update-locale LANG=en_US.UTF-8 \     # Настраиваем пользователя и группу     && addgroup --gid $GROUP_ID $GROUP_NAME \     && adduser -q --gecos '' --disabled-password --ingroup $GROUP_NAME --shell /bin/bash --uid $USER_ID $USER_NAME \     # Отчищаем кэш     && apt-get clean \     && rm -rf /tmp/* /var/lib/apt/lists/* \     # Создаем необходимые директории и файлы     && mkdir -p /mnt/resource/static \     && mkdir -p /mnt/resource/media \     && mkdir -p $APPLICATION_PATH \     && touch $APPLICATION_PATH/celerybeat-schedule \     # Настраиваем права к директориям     && chown -R $USER_ID:$GROUP_ID /tmp/ \     && chown -R $USER_ID:$GROUP_ID /mnt/ \     && chown -R $USER_ID:$GROUP_ID /opt/ \     && chown -R $USER_ID:$GROUP_ID /project/ \     && chown -R $USER_ID:$GROUP_ID /var/log/  # ---STAGE-7--- # Стадия сборки приложения для dev среды FROM base-project-builder AS development  # Копируем необходимые данные из предыдущих стадий COPY --link --chown=$USER_ID:$GROUP_ID --from=development-pdm-builder $PDM_HOME $PDM_HOME COPY --link --chown=$USER_ID:$GROUP_ID --from=development-pdm-builder $PROJECT_PATH $PROJECT_PATH # Копируем код приложения с хоста COPY --link ./src $APPLICATION_PATH # Копируем конфигурацию и наполнение документации COPY --link ./docs $APPLICATION_PATH/docs COPY --link ./mkdocs.yml $APPLICATION_PATH/mkdocs.yml  # Создвем символическую ссылку на pyproject.toml для корректной работы PDM  и удаляем не нужные в образе файлы RUN ln -s $PROJECT_PATH/pyproject.toml $APPLICATION_PATH/pyproject.toml \     && find . -name "*.pyc" -delete  # Меняем пользователя USER $USER_NAME  # ---STAGE-8--- # Стадия сборки приложения для test среды FROM base-project-builder AS test  # Копируем необходимые данные из предыдущих стадий COPY --link --from=test-pdm-builder $PDM_HOME $PDM_HOME COPY --link --from=test-pdm-builder $PROJECT_PATH $PROJECT_PATH # Копируем код приложения с хоста COPY --link ./src $APPLICATION_PATH   RUN chown $USER_ID:$GROUP_ID $PROJECT_PATH/pyproject.toml \     && ln -s $PROJECT_PATH/pyproject.toml $APPLICATION_PATH/pyproject.toml \     && find . -name "*.pyc" -delete  # Меняем пользователя USER $USER_NAME  # ---STAGE-9--- # Стадия сборки приложения для prod среды FROM base-project-builder AS production  # Копируем необходимые данные из предыдущих стадий COPY --link --from=production-pdm-builder $PDM_HOME $PDM_HOME COPY --link --from=production-pdm-builder $PROJECT_PATH $PROJECT_PATH # Копируем код приложения с хоста COPY --link ./src $APPLICATION_PATH  # Меняем пользователя USER $USER_NAME 

Запуск проекта

Зависимости:

  • python version — 3.12.6

  • docker — 27.2.1

Точка входа

Запуск проекта осуществляется с помощью скрипта compose
По факту он просто подготавливает окружение, подтягивает переменные среды и прокидывает передаваемые аргументы в docker compose.

Если вы используете более раннюю версию docker вам нужно использовать команду docker-compose вместо docker compose

#!/bin/bash  bash .prepare.sh source ".env"  ARGS_STRING="" while [ $1 ]     do         ARGS_STRING+="$1 "         shift     done  docker compose ${ARGS_STRING}

Далее все как обычно

./compose up -d --build

./compose restart

./compose down

./compose exec -it ...

...

Управление зависимостями

Для управления зависимостями используется pdm v2.19.1

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

./compose exec -it asgi pdm add <dependency>

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

./compose exec -it asgi pdm add -dG <group> <dependency>

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

./compose exec -it asgi pdm remove <dependency>

Прочие файлы

В данном разделе представлены файлы из моего каркаса приложения, которые также могут быть интересны в формате текущей статьи но не несут за собой фундаментальной значимости для освещаемой темы

Пример конфигурации pyproject.toml
[build-system] requires = ["pdm-backend"] build-backend = "pdm.backend"  [project] name = "YOUR_PROJECT" dynamic = ["version"] description = "Основное приложение для YOUR_PROJECT" readme = "README.md" requires-python = ">=3.12" authors = [     { name = "Your Name", email = "your@email.ru"}, ] maintainers = [     { name = "Your Name", email = "your@email.ru"}, ]  classifiers = [     "Development Status :: 3 - Alfa",     "Operating System :: OS Independent",     "Programming Language :: Python",     "Programming Language :: Python :: 3",     "Programming Language :: Python :: 3 :: Only",     "Programming Language :: Python :: 3.12",     "Framework :: Django",     "Framework :: Pydantic", ]  # Набор библиотек, которые вам скорее всего понадобятся, если вы что-то делаете на django dependencies = [     # Чисто django     "django>=5.1.1",     "djangorestframework>=3.15.2",     "django-extensions>=3.2.3",     # Для CORS в джанго     "django-cors-headers>=4.4.0",     "whitenoise>=6.7.0",     # Для работы с celery     "celery>=5.4.0",     "django-celery-beat>=2.7.0",     "django-celery-results>=2.5.1",     "flower>=2.0.1",     # Для работы с redis (в новых версиях с ним теперь можно работать и синхронно и асинхронно)     "redis>=5.1.0",     "django-redis>=5.4.0",     # Я использую Pydantic для сериализации и валидации     "pydantic[email]>=2.9.2",     "pydantic-extra-types>=2.9.0",     # Супер удобная либа для работы с хранением файлов     "django-storages[s3]>=1.14.4",     # Для веб сервера     "uvicorn>=0.31.0",     "gunicorn>=23.0.0",     "uvicorn-worker>=0.2.0",     # Супер удобная либа для работы с переменными окружения     "envparse>=0.2.0",     # Прочее     "toml>=0.10.2",     "markdown>=3.7",     "psycopg2-binary>=2.9.9",     "sentry-sdk[celery]>=2.15.0",     "mock>=5.1.0",     "IPython>=8.29.0",     "requests>=2.32.3",     "pyjwt>=2.10.1",     "Pillow>=11.1.0", ]  [tool.pdm] version = { source = "file", path = "src/__init__.py" } distribution = true  # Стандартные группы и либы, которые я использую для разных сборок в большинстве своих проектов [tool.pdm.dev-dependencies] # Тесты test = [     "pytest>=8.3.3",     "coverage>=7.6.1",     "pytest-cov>=6.0.0",     "pytest-django>=4.9.0",     "pytest-celery>=1.1.3", ] # Документация doc = [     "mkdocs>=1.6.1",     "mkdocstrings[python]>=0.26.1",     "mkdocs-material>=9.5.39", ] # Линтеры lint = [     "black>=24.8.0",     "ruff>=0.6.8", ] # Прекоммит dev = [     "pre-commit>=3.8.0", ]  [project.urls] Homepage = "YOUR_HOME_PAGE" Repository = "YOUR_REPOSITORY"  # Очень удобная либа для бампа версий bump-my-version [tool.bumpversion] current_version = "0.0.2" tag = true tag_name = "v{new_version}" tag_message = "Bump version: {current_version} → {new_version}" commit = true message = "Bump version: {current_version} → {new_version}"      [[tool.bumpversion.files]]     filename = "src/__init__.py"      [[tool.bumpversion.files]]     filename = "README.md"     search = "Версия - v{current_version}"     replace = "Версия - v{new_version}"  [tool.ruff] target-version = "py312" line-length = 120 extend-exclude = [     ".git",     ".git-rewrite",     ".ruff_cache",     ".mypy_cache",     ".venv",     ".local_files",     "__pypackages__",     "fixtures",     "migrations/versions", ]  [tool.ruff.lint] # Стандартные настройки линтера тут часто бывает вкусовщина select = [     "E",  # pycodestyle errors     "W",  # pycodestyle warnings     "F",  # pyflakes     "I",  # isort     "C",  # flake8-comprehensions     "B",  # flake8-bugbear     "S",  # flake8-bandit     "Q",  # flake8-quotes     "PL", # Pylint     "RUF100", # Unused noqa directive ] ignore = [     "B008",  # do not perform function calls in argument defaults     "B904",  # use 'raise ... from err'     "B905",  # use explicit 'strict=' parameter with 'zip()'     "C901",  # too complex     "C417",  # Unnecessary `map` usage (rewrite using a `list` comprehension)     "F403",  # used; unable to detect undefined names     "F405",  # may be undefined, or defined from star imports     "E501",  # Line too long handled by black     "N818",  # Exception name should be named with an Error suffix     "S308",  # mark_safe     "S603",  # mark_safe     "S607",  # mark_safe     "PLR0911", # Too many return statements     "PLR0912", # Too many branches     "PLR0913", # Too many arguments to function call     "PLR0915", # Too many statements     "PLW2901", # Loop variable overwritten by assignment target ]  [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"]  # Настройки запуска тестов [tool.pytest.ini_options] # С данными аргументами тесты: #   - не будут запускать миграции  #   - будут запускать сначала упавшие тесты  #   - остановятся после первого провала #   - создадут тестовую бд #   - запустятся с выбранной настройкой django addopts = "--no-migrations --failed-first --verbose --maxfail=1 -p no:warnings --create-db --ds=settings.test_settings" norecursedirs = ["static", "migrations", "templates"] python_files = "test_*.py" django_find_project = false  [tool.coverage.run] # Не отображать в отчете покрытия тестами omit = ['*/tests/*', '*/migrations/*', 'settings/*'] 

Конфигурация mkdocs
site_name: YOUR_SITE nav:   - Главная: index.md  theme:   name: material   highlightjs: true   hljs_languages:     - yaml     - python   language: ru   features:     - navigation.instant     - navigation.instant.progress     - navigation.tracking     - navigation.tabs     - navigation.tabs.sticky     - navigation.sections     - navigation.expand     - navigation.path     - navigation.indexes     - navigation.top     - toc.follow  markdown_extensions:   - admonition   - codehilite   - pymdownx.betterem   - pymdownx.caret   - pymdownx.mark   - pymdownx.tilde   - pymdownx.details   - pymdownx.highlight:       anchor_linenums: true   - pymdownx.superfences   - pymdownx.inlinehilite  plugins: - autorefs - search - tags - mkdocstrings:     default_handler: python     handlers:       python:         import:         # latest instead of stable         - http://python-eve.org/objects.inv         options:           extensions:           - griffe_inherited_docstrings           find_stubs_package: true           summary: true           relative_crossrefs: true           merge_init_into_class: true           inherited_members: true           allow_inspection: true           annotations_path: source           line_length: 60           separate_signature: true           signature_crossrefs: false           modernize_annotations: true           docstring_style: google           docstring_section_style: spacy           docstring_options:             trim_doctest_flags: true             warn_unknown_params: true             ignore_init_summary: true           show_docstring_attributes: false           show_docstring_functions: true           show_docstring_classes: true           show_docstring_modules: true           show_docstring_description: true           show_docstring_examples: true           show_docstring_other_parameters: true           show_docstring_parameters: true           show_docstring_raises: true           show_docstring_receives: true           show_docstring_returns: true           show_docstring_warns: true           show_docstring_yields: true           show_root_heading: true           show_root_toc_entry: true           show_root_full_path: false           show_root_members_full_path: false           show_symbol_type_heading: true           show_symbol_type_toc: true           show_source: true           show_object_full_path: false           show_signature: true           show_signature_annotations: true           show_category_heading: true           show_if_no_docstring: true           show_labels: true           show_bases: true           show_inheritance_diagram: true           show_submodules: false           heading_level: 2           parameter_headings: true           members_order: source           group_by_category: true           paths:             - /srs           preload_modules:             - __future__             - pydantic             - datetime             - abc           filters:             - "!^_"             - "__"             - "!^__slots__"             - "!^__all__"

Пример .pre-commit-config.yaml
default_language_version:     python: python3.12  repos:   - repo: https://github.com/pre-commit/pre-commit-hooks     rev: v5.0.0     hooks:       - id: check-added-large-files       - id: check-toml       - id: check-yaml         args:           - --unsafe       - id: trailing-whitespace       - id: end-of-file-fixer   - repo: https://github.com/asottile/pyupgrade     rev: v3.19.0     hooks:     -   id: pyupgrade         args: [--py312-plus]   - repo: https://github.com/psf/black     rev: 24.8.0     hooks:     -   id: black         args: [src, --config, ./pyproject.toml, --skip-magic-trailing-comma]   - repo: https://github.com/charliermarsh/ruff-pre-commit     rev: v0.7.3     hooks:       - id: ruff         args:           - --fix       - id: ruff-format   - repo: https://github.com/asottile/reorder-python-imports     rev: v3.14.0     hooks:       - id: reorder-python-imports         args: [--py312-plus] 

Пример .gitignore
/.env.local /.pdm-python /.venv/ /.ruff_cache/ /.cache/ /.deploy/ /.local_files/ /src/settings/local_settings.py /src/migrations/versions/ /src/celerybeat-schedule /src/celerybeat-schedule.db /src/settings/local.py /shell /restore_postgres /manage /dbshell /bash /.env /gl-code-quality-report.json /docker/composes/docker-compose.override.yml /init_clean_project venv/ .idea/ /src/static/ /src/report.xml /src/coverage.xml /src/.coverage /ca.crt /src/report.html

Пример .dockerignore
# Distribution / packaging / PyCharm .Python .idea/ .venv/  # Project local /.local_files/ /.ruff_cache/ .env .env.local  # Project /pipelines/ /scripts/ /src/celerybeat-schedule /src/coverage.xml /src/.coverage /src/report.xml /src/settings/local_settings.py  # Git .git .gitignore .gitattributes  # CI .gitlab-ci.yml  # Docker .docker .dockerignore docker-compose.yml **/dockerfiles **/composes  # Byte-compiled / optimized / DLL files **/__pycache__/ **/*.py[cod]

Пример базовых настроек base_settings.py
from __future__ import annotations  from pathlib import Path  import toml from envparse import env  # Путь до корня src проекта BASE_DIR = Path(__file__).resolve().parent.parent # Метаданные из toml файла with Path(BASE_DIR.parent, "pyproject.toml").open("r") as toml_file:     TOML_METADATA = toml.load(toml_file)  # Версия проекта VERSION = TOML_METADATA["tool"]["bumpversion"]["current_version"] # При DEBUG = True будет работать hot reload для uvicorn DEBUG = env.bool("DEBUG", default=False) # Продакшн режим PRODUCTION = env.bool("PRODUCTION", default=False) # Тестовый режим TEST_MODE = False # Ключ секретов для генерации токенов SECRET_KEY = env.str("SECRET_KEY", default="")  # Адрес сайта SITE_PROTOCOL = env.str("SITE_PROTOCOL", default="http") SITE_DOMAIN = env.str("SITE_DOMAIN", default="127.0.0.1") SITE_PORT = env.str("SITE_PORT", default=None) SITE_URL = f"{SITE_PROTOCOL}://{SITE_DOMAIN}" SITE_API_PATH = env.str("SITE_API_PATH", default="api/v1")  if SITE_PORT:     SITE_URL = f"{SITE_URL}:{SITE_PORT}"  SITE_API_URL = f"{SITE_URL}/{SITE_API_PATH}"

Пример конфигурации Nginx

Прошу обратить внимания на то, что данная конфигурация достаточно скудная, сам я ее считаю более чем позорной, но тем не менее она является достаточной для локальной разработки

# nginx.conf  server {      listen 0.0.0.0:${NGINX_PORT_INTERNAL};     server_name 0.0.0.0 ${HOST_NAME};      set $backend asgi:${ASGI_PORT_INTERNAL};     set $docs docs:${DOCS_PORT_INTERNAL};     resolver 127.0.0.11 valid=10s;      client_max_body_size 100M;      location /media/ {         alias /media/;         autoindex off;     }      location /docs/ {         proxy_pass http://$docs;         proxy_http_version 1.1;         proxy_set_header Upgrade $http_upgrade;         proxy_set_header Connection "upgrade";         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_pass_request_headers on;         proxy_connect_timeout 1800;         proxy_send_timeout    1800;         proxy_read_timeout    1800;         send_timeout          1800;     }      location / {         proxy_pass http://$backend;         proxy_http_version 1.1;         proxy_set_header Upgrade $http_upgrade;         proxy_set_header Connection "upgrade";         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_pass_request_headers on;         proxy_connect_timeout 1800;         proxy_send_timeout    1800;         proxy_read_timeout    1800;         send_timeout          1800;     } } 

Заключение

В данной статье мы осветили основные концепции и идеи того, как можно делать удобные и понятные окружения для любых проектов. Очень надеюсь, что ничего фундаментально важного не забыл и вы смогли найти в этой статье что-то новое для себя. Буду рад любой критике или предложениям по улучшению как статьи так и самого подхода к организации окружения и проекта в целом. Сильно вдаваться в детали не стал, много о чем еще можно поговорить на данную тему, буду рад побеседовать с вами в комментариях к статье.


ссылка на оригинал статьи https://habr.com/ru/articles/881882/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *