<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IaC on kenji.blog</title><link>http://kenji.blog/ru/tags/iac/</link><description>Recent content in IaC on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 01:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ru/tags/iac/index.xml" rel="self" type="application/rss+xml"/><item><title>Создание воспроизводимой локальной среды разработки с использованием Docker</title><link>http://kenji.blog/ru/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/docker-reproducible-local-dev-environment/</guid><description>&lt;img src="http://kenji.blog/p/docker-reproducible-local-dev-environment/img/eyecatch.jpg" alt="Featured image of post Создание воспроизводимой локальной среды разработки с использованием Docker" />&lt;h2 id="1-введение-избавление-от-проблемы-на-моей-машине-это-работает">1. Введение: Избавление от проблемы «на моей машине это работает»
&lt;/h2>&lt;p>В сфере разработки программного обеспечения проблема «на моей машине это работает (It works on my machine)», вызванная различиями в средах разработчиков, долгое время была причиной потери времени во многих проектах. Из-за различий в ОС, установленных версиях языков, зависимостях библиотек и конфликтах глобально установленных инструментов локальная среда постоянно подвергается «неопределенности состояния».&lt;/p>
&lt;p>Эту проблему в корне решают контейнерные технологии, такие как &lt;strong>Docker&lt;/strong>, и парадигма &lt;strong>Infrastructure as Code (IaC)&lt;/strong>. Контейнеризация локальной среды разработки обеспечивает изоляцию на уровне ОС и позволяет контролировать версии самой среды вместе с кодовой базой.&lt;/p>
&lt;p>В этой статье мы подробно, в том числе с математической точки зрения, рассмотрим глубокие технические механизмы и шаги для создания &lt;strong>«воспроизводимой локальной среды разработки, которая будет абсолютно одинаковой независимо от того, кто, когда и на какой машине ее запускает»&lt;/strong>, используя Docker, Docker Compose и VSCode DevContainers.&lt;/p>
&lt;hr>
&lt;h2 id="2-совместимость-infrastructure-as-code-iac-и-контейнерных-технологий">2. Совместимость Infrastructure as Code (IaC) и контейнерных технологий
&lt;/h2>&lt;h3 id="принципы-iac-и-их-применение-к-локальной-среде">Принципы IaC и их применение к локальной среде
&lt;/h3>&lt;p>Infrastructure as Code (IaC) — это подход, при котором настройка и выделение ресурсов инфраструктуры управляются не через ручные процессы, а через машиночитаемые конфигурационные файлы. Ключевые принципы IaC включают следующие элементы:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Декларативный подход (Declarative Approach)&lt;/strong>: Определяется не «как изменить состояние», а «каким должно быть конечное состояние».&lt;/li>
&lt;li>&lt;strong>Идемпотентность (Idempotency)&lt;/strong>: Независимо от того, сколько раз выполняется скрипт, всегда гарантируется один и тот же результат (состояние).&lt;/li>
&lt;li>&lt;strong>Контроль версий (Version Control)&lt;/strong>: Состояние инфраструктуры сохраняется в виде кода в VCS, таких как Git, что позволяет отслеживать историю изменений и проводить код-ревью (peer review).&lt;/li>
&lt;/ol>
&lt;p>Применение IaC в локальной среде разработки означает кодирование «идеального состояния» среды разработки с использованием &lt;code>Dockerfile&lt;/code>, &lt;code>docker-compose.yml&lt;/code> и &lt;code>devcontainer.json&lt;/code>. Это позволяет новым участникам команды моментально приступить к разработке, просто клонировав репозиторий и выполнив одну команду, обеспечивая идеальный опыт онбординга.&lt;/p>
&lt;h3 id="функции-ядра-лежащие-в-основе-контейнерных-технологий">Функции ядра, лежащие в основе контейнерных технологий
&lt;/h3>&lt;p>Технология контейнеров — это легковесная технология виртуализации, которая изолирует процессы, разделяя ядро хост-ОС, в отличие от виртуализации на основе гипервизора, такой как виртуальные машины (VM). Для достижения этого в основном используются следующие функции ядра Linux:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Namespaces&lt;/strong>: Обеспечивают независимое представление системных ресурсов (PID, сеть, точки монтирования, пользователи и т. д.) для каждого процесса.&lt;/li>
&lt;li>&lt;strong>Cgroups (Control Groups)&lt;/strong>: Ограничивают и распределяют физические ресурсы (процессор, память, дисковый ввод-вывод и т. д.), доступные процессам.&lt;/li>
&lt;li>&lt;strong>UnionFS (Union File System)&lt;/strong>: Технология, которая прозрачно накладывает несколько деревьев каталогов (слоев) друг на друга, представляя их как единую файловую систему. Слои образов Docker опираются на эту технологию.&lt;/li>
&lt;/ul>
&lt;p>Давайте рассмотрим математическую модель ограничения ресурсов. Пусть $M_{\text{total}}$ — общий объем памяти хост-машины, а $m_i$ — ограничение памяти для $n$ контейнеров, работающих на хосте. Необходимое условие для стабильной работы системы, с учетом базовой памяти $M_{\text{os}}$, потребляемой хост-ОС и другими процессами, можно выразить следующим неравенством:&lt;/p>
$$ \sum_{i=1}^{n} m_i \le M_{\text{total}} - M_{\text{os}} $$&lt;p>Строго определяя $m_i$ для каждого контейнера с помощью Cgroups, даже если в конкретном контейнере происходит утечка памяти, механизм OOM (Out Of Memory) Killer предотвратит сбой других контейнеров или всей хост-системы.&lt;/p>
&lt;hr>
&lt;h2 id="3-эффективное-проектирование-dockerfile-освоение-многоэтапных-сборок">3. Эффективное проектирование Dockerfile: Освоение многоэтапных сборок
&lt;/h2>&lt;p>Первым шагом к воспроизводимой среде является проектирование &lt;code>Dockerfile&lt;/code>, определяющего среду выполнения приложения. Здесь на примере Python (FastAPI) мы рассмотрим лучшие практики создания безопасного и легковесного Dockerfile с использованием &lt;strong>многоэтапных сборок (multi-stage builds)&lt;/strong>.&lt;/p>
&lt;p>Многоэтапная сборка — это метод, при котором в одном &lt;code>Dockerfile&lt;/code> используется несколько инструкций &lt;code>FROM&lt;/code>, разделяя среду сборки (тяжелую среду, содержащую компиляторы и инструменты разработки) и среду выполнения (легковесную среду, содержащую только необходимые артефакты).&lt;/p>
&lt;h3 id="практический-dockerfile-для-python-fastapi">Практический Dockerfile для Python FastAPI
&lt;/h3>&lt;p>Ниже приведен пример продвинутого &lt;code>Dockerfile&lt;/code>, объединяющего управление зависимостями с помощью Poetry и многоэтапную сборку.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 1: Builder (Среда сборки)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS builder&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Настройка необходимых переменных окружения&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>.6.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_HOME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/opt/poetry&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VIRTUALENVS_IN_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_NO_INTERACTION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Установка зависимостей&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apt-get update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y --no-install-recommends &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl build-essential &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -sSL https://install.python-poetry.org &lt;span class="p">|&lt;/span> python3 - &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> apt-get clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$POETRY_HOME&lt;/span>&lt;span class="s2">/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование файлов зависимостей и установка&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install --no-root --only main&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 2: Runtime (Среда выполнения)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS runtime&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/app/.venv/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Создание минимального непривилегированного пользователя&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> groupadd -r appuser &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> useradd -r -g appuser appuser&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование только виртуальной среды (зависимостей) из сборщика&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --from&lt;span class="o">=&lt;/span>builder --chown&lt;span class="o">=&lt;/span>appuser:appuser /app/.venv /app/.venv&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование кода приложения&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --chown&lt;span class="o">=&lt;/span>appuser:appuser ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Переключение на непривилегированного пользователя&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> appuser&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Команда по умолчанию при запуске контейнера&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="математическая-оценка-размера-образа-благодаря-многоэтапной-сборке">Математическая оценка размера образа благодаря многоэтапной сборке
&lt;/h3>&lt;p>Пусть размер образа при одноэтапной сборке равен $S_{\text{single}}$, а при многоэтапной — $S_{\text{multi}}$. Коэффициент уменьшения размера $R$ вычисляется следующим образом:&lt;/p>
$$ R = \left( 1 - \frac{S_{\text{multi}}}{S_{\text{single}}} \right) \times 100 \ (\%) $$&lt;p>Например, предположим, что $S_{\text{single}}$ включает базовый образ ОС (около 110 МБ), пакеты для разработки (например, gcc, около 150 МБ), сам Poetry (около 40 МБ), библиотеки зависимостей проекта (около 80 МБ) и исходный код (около 5 МБ), что в сумме составляет 385 МБ.
С другой стороны, в $S_{\text{multi}}$ в базовый образ (110 МБ) копируются только библиотеки зависимостей (80 МБ) и исходный код (5 МБ), что дает в сумме 195 МБ.&lt;/p>
$$ R = \left( 1 - \frac{195}{385} \right) \times 100 \approx 49.35\% $$&lt;p>Таким образом, внедрение многоэтапной сборки позволяет сократить размер образа примерно вдвое. Уменьшение размера образа напрямую связано с сокращением времени загрузки (Pull) из реестра, экономией дискового пространства и повышением безопасности за счет уменьшения поверхности атаки (Attack Surface).&lt;/p>
&lt;hr>
&lt;h2 id="4-оркестрация-нескольких-контейнеров-с-помощью-docker-compose">4. Оркестрация нескольких контейнеров с помощью Docker Compose
&lt;/h2>&lt;p>В современной веб-разработке обычно используется микросервисная архитектура, где взаимодействуют несколько компонентов, таких как веб-серверы, базы данных и кэш-серверы. Для централизованного управления ими в локальной среде используется &lt;code>docker-compose.yml&lt;/code>.&lt;/p>
&lt;p>В этот раз мы создадим трехуровневую систему, состоящую из «Web (FastAPI)», «Database (PostgreSQL)» и «Cache (Redis)» в локальной среде.&lt;/p>
&lt;h3 id="схема-архитектуры-mermaid">Схема архитектуры (Mermaid)
&lt;/h3>&lt;p>На следующей схеме представлена блочная диаграмма, показывающая взаимосвязь каждого контейнера, сети и томов на локальной машине.&lt;/p>
&lt;pre class="mermaid">
graph TD
User[&amp;#34;Хост-машина (Браузер/curl)&amp;#34;] --&amp;gt;|Localhost:8000| Web[&amp;#34;Веб-контейнер FastAPI&amp;#34;]
subgraph &amp;#34;Мостовая сеть Docker (app-network)&amp;#34;
Web --&amp;gt;|Port 5432| DB[&amp;#34;Контейнер PostgreSQL&amp;#34;]
Web --&amp;gt;|Port 6379| Redis[&amp;#34;Контейнер Redis&amp;#34;]
end
DB --&amp;gt; Volume1[&amp;#34;Именованный том (postgres_data)&amp;#34;]
Redis --&amp;gt; Volume2[&amp;#34;Именованный том (redis_data)&amp;#34;]
HostDir[&amp;#34;Исходный код хоста (./src)&amp;#34;] -.-&amp;gt;|Bind Mount| Web
&lt;/pre>
&lt;h3 id="реализация-и-подробное-объяснение-docker-composeyml">Реализация и подробное объяснение docker-compose.yml
&lt;/h3>&lt;p>Ниже приведен пример надежного &lt;code>docker-compose.yml&lt;/code>, подходящего для практического создания среды.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3.8&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">runtime&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;8000:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./src:/app/src:ro &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Монтирование кода хоста только для чтения (для горячей перезагрузки)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">REDIS_URL=redis://redis:6379/0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env_file&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_started&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--reload&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres:15-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;5432:5432&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_PASSWORD}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">postgres_data:/var/lib/postgresql/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CMD-SHELL&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;pg_isready -U postgres -d ${POSTGRES_DB}&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">redis:7-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_redis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;6379:6379&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">redis_data:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;redis-server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--appendonly&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">postgres_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app-network&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bridge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="тома-volumes-и-сохранение-данных">Тома (Volumes) и сохранение данных
&lt;/h3>&lt;p>Контейнеры, как правило, «не имеют состояния (stateless)» и «эфемерны (ephemeral)». При уничтожении контейнера данные внутри него также теряются. Чтобы сохранить данные базы данных или кэш, необходимо смонтировать область файловой системы хост-машины в контейнер.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bind Mount (Привязка монтирования)&lt;/strong>: Этому соответствует &lt;code>./src:/app/src:ro&lt;/code> в службе &lt;code>web&lt;/code> выше. Он напрямую отображает определенный каталог на хосте в контейнер. Используется для немедленного отражения локального редактирования кода в контейнере (горячая перезагрузка). С точки зрения безопасности рекомендуется добавить опцию &lt;code>:ro&lt;/code> (Read-Only), чтобы код на хосте нельзя было изменить со стороны контейнера.&lt;/li>
&lt;li>&lt;strong>Named Volume (Именованный том)&lt;/strong>: К ним относятся &lt;code>postgres_data&lt;/code> и &lt;code>redis_data&lt;/code>. Это области, внутренне управляемые Docker (например, &lt;code>/var/lib/docker/volumes/&lt;/code>), которые превосходят Bind Mount по производительности ввода-вывода и сглаживают различия файловых систем между ОС. Их всегда следует использовать для сохранения баз данных.&lt;/li>
&lt;/ul>
&lt;h3 id="сеть-networking-и-обнаружение-сервисов">Сеть (Networking) и обнаружение сервисов
&lt;/h3>&lt;p>По умолчанию Docker Compose создает собственную мостовую сеть для каждого проекта. В данном случае это &lt;code>app-network&lt;/code>.
Контейнеры, принадлежащие одной сети, могут разрешать имена (DNS-разрешение) друг друга, используя «имя службы (например: &lt;code>db&lt;/code>, &lt;code>redis&lt;/code>)» в качестве имени хоста вместо IP-адреса.
Например, из веб-контейнера можно получить доступ к базе данных по URL &lt;code>postgresql://postgres:password@db:5432/mydb&lt;/code>. Это позволяет прозрачно переключать места подключения через переменные окружения как в локальной, так и в рабочей (production) среде.&lt;/p>
&lt;h3 id="проверка-работоспособности-healthcheck-и-контроль-порядка-запуска">Проверка работоспособности (Healthcheck) и контроль порядка запуска
&lt;/h3>&lt;p>Директива &lt;code>depends_on&lt;/code> управляет порядком запуска контейнеров, но простое указание &lt;code>depends_on&lt;/code> запустит веб-контейнер на этапе, когда «запущен контейнер БД». На самом деле, процесс инициализации БД (запуск процесса PostgreSQL и подготовка таблиц) занимает несколько секунд, поэтому подключение к БД из веб-контейнера может привести к ошибке.
Чтобы предотвратить это, можно определить &lt;code>healthcheck&lt;/code> и указать &lt;code>condition: service_healthy&lt;/code>, что позволит запускать веб-контейнер только после того, как будет подтверждено, что «БД готова принимать запросы на подключение».&lt;/p>
&lt;hr>
&lt;h2 id="5-управление-переменными-окружения-и-безопасность-env">5. Управление переменными окружения и безопасность (.env)
&lt;/h2>&lt;p>Жесткое кодирование (hardcoding) конфиденциальной информации, такой как пароли баз данных или ключи API, в &lt;code>docker-compose.yml&lt;/code> является антипаттерном, которого следует абсолютно избегать. Вместо этого для внедрения этих значений используется файл переменных окружения &lt;code>.env&lt;/code>.&lt;/p>
&lt;p>Создайте файл &lt;code>.env&lt;/code> в корне проекта.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Файл .env (обязательно добавьте в .gitignore, чтобы исключить из-под контроля Git)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">supersecretpassword&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_DB&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">devdb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">API_SECRET_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">dev_secret_key_12345&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>По умолчанию Docker Compose считывает файл &lt;code>.env&lt;/code> в рабочем каталоге и разворачивает заполнители вида &lt;code>${VAR_NAME}&lt;/code> в YAML-файле. Этот метод позволяет безопасно управлять различными настройками для разных сред, таких как локальная, staging и production, без изменения кода инфраструктуры.&lt;/p>
&lt;hr>
&lt;h2 id="6-vscode-devcontainers-идеальный-опыт-разработки">6. VSCode DevContainers: Идеальный опыт разработки
&lt;/h2>&lt;p>На данный момент мы создали надежную бэкенд-среду с использованием Docker. Однако можно пойти еще дальше. Используя функцию &lt;strong>VSCode DevContainers (Remote - Containers)&lt;/strong>, можно запускать бэкенд самого редактора (VSCode) внутри контейнера.&lt;/p>
&lt;p>Благодаря этому отпадает необходимость устанавливать даже Python или Node.js на локальную машину. Линтеры (flake8/eslint), форматтеры (black/prettier) и даже расширения IDE — всё это можно определить в кодовой базе и использовать совместно со всей командой.&lt;/p>
&lt;h3 id="настройка-devcontainerjson">Настройка devcontainer.json
&lt;/h3>&lt;p>Создайте каталог &lt;code>.devcontainer&lt;/code> в корне проекта и поместите в него файл конфигурации.&lt;/p>
&lt;p>&lt;code>.devcontainer/devcontainer.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Python FastAPI Dev Environment&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dockerComposeFile&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;../docker-compose.yml&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;service&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;web&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;workspaceFolder&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customizations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;vscode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;settings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.defaultInterpreterPath&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app/.venv/bin/python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.formatting.provider&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;editor.formatOnSave&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;extensions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.vscode-pylance&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.black-formatter&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tamasfe.even-better-toml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;forwardPorts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5432&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6379&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;remoteUser&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;appuser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;postCreateCommand&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;poetry install&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Включив этот файл в репозиторий, при открытии проекта в VSCode сразу же появится запрос «Reopen in Container» (Открыть в контейнере). По одному щелчку мыши запустятся все необходимые контейнеры, установятся расширения, и вы будете готовы немедленно начать писать код. Это поистине волшебный опыт.&lt;/p>
&lt;hr>
&lt;h2 id="7-последовательность-обработки-запросов-и-моделирование-производительности">7. Последовательность обработки запросов и моделирование производительности
&lt;/h2>&lt;p>Давайте рассмотрим жизненный цикл обработки запросов веб-приложения в созданной локальной среде разработки с помощью диаграммы последовательности и обсудим математическую модель ее производительности.&lt;/p>
&lt;h3 id="диаграмма-последовательности-поток-запроса">Диаграмма последовательности (Поток запроса)
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant Client as &amp;#34;Браузер / VSCode&amp;#34;
participant Web as &amp;#34;FastAPI (Web)&amp;#34;
participant Redis as &amp;#34;Кэш Redis&amp;#34;
participant DB as &amp;#34;PostgreSQL&amp;#34;
Client-&amp;gt;&amp;gt;Web: &amp;#34;GET /api/users/123&amp;#34;
activate Web
Web-&amp;gt;&amp;gt;Redis: &amp;#34;Проверка кэша для user:123&amp;#34;
activate Redis
alt &amp;#34;Попадание в кэш (Данные есть)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Возврат кэшированных данных пользователя&amp;#34;
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Быстрый ответ)&amp;#34;
else &amp;#34;Промах кэша (Данных нет)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Null (Не найдено)&amp;#34;
deactivate Redis
Web-&amp;gt;&amp;gt;DB: &amp;#34;SELECT * FROM users WHERE id = 123&amp;#34;
activate DB
DB--&amp;gt;&amp;gt;Web: &amp;#34;Возврат строки базы данных&amp;#34;
deactivate DB
Web-&amp;gt;&amp;gt;Redis: &amp;#34;SET данные user:123 (TTL: 60s)&amp;#34;
activate Redis
Redis--&amp;gt;&amp;gt;Web: &amp;#34;OK&amp;#34;
deactivate Redis
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Стандартный ответ)&amp;#34;
end
deactivate Web
&lt;/pre>
&lt;h3 id="математическая-модель-задержки-обработки-latency">Математическая модель задержки обработки (Latency)
&lt;/h3>&lt;p>Математически смоделируем среднее время обработки запроса $T_{\text{total}}$ в вышеописанной системе.
Задержки для каждой операции определены следующим образом:&lt;/p>
&lt;ul>
&lt;li>$T_{\text{net}}$: Сетевая задержка между клиентом и веб-контейнером&lt;/li>
&lt;li>$T_{\text{app}}$: Чистое время обработки на стороне приложения (сериализация и т. д.)&lt;/li>
&lt;li>$T_{\text{cache}}$: Время, затрачиваемое на чтение и запись из Redis&lt;/li>
&lt;li>$T_{\text{db}}$: Время, затрачиваемое на выполнение запроса к PostgreSQL&lt;/li>
&lt;li>$p_{\text{miss}}$: Вероятность промаха кэша ($0 \le p_{\text{miss}} \le 1$)&lt;/li>
&lt;/ul>
&lt;p>Тогда среднее время отклика выражается следующей формулой ожидаемого значения:&lt;/p>
$$ T_{\text{total}} = T_{\text{net}} + T_{\text{app}} + T_{\text{cache}} + p_{\text{miss}} \times (T_{\text{db}} + T_{\text{cache\_write}}) $$&lt;p>В локальной среде разработки (внутри Docker) $T_{\text{net}}$ близко к 0, но стоит обратить внимание на &lt;strong>производительность ввода-вывода (I/O) при Bind Mount&lt;/strong>. В частности, при использовании Docker Desktop на Windows/macOS из-за накладных расходов на совместное использование файлов между хост-ОС и виртуальной машиной (контейнером) $T_{\text{app}}$ (например, время загрузки кода) имеет тенденцию увеличиваться. Чтобы устранить это узкое место, настоятельно рекомендуется архитектура, в которой используется описанный ранее DevContainers для размещения всего исходного кода внутри именованного тома, или работа движка Docker нативно в среде WSL2 (Windows Subsystem for Linux 2).&lt;/p>
&lt;hr>
&lt;h2 id="8-оптимизация-производительности-сборки-docker-стратегия-кэширования-слоев">8. Оптимизация производительности сборки Docker: Стратегия кэширования слоев
&lt;/h2>&lt;p>Понимание механизма «кэширования слоев» при написании Dockerfile кардинально меняет время сборки.
Docker создает разницу в файловой системе (слой) для каждой инструкции Dockerfile (например, &lt;code>FROM&lt;/code>, &lt;code>RUN&lt;/code>, &lt;code>COPY&lt;/code>) и сохраняет ее в виде кэша. При повторной сборке повторно используется кэш для слоев, которые не изменились.&lt;/p>
&lt;p>Важный принцип заключается в том, чтобы &lt;strong>«описывать, начиная с того, что меняется реже всего»&lt;/strong>.&lt;/p>
&lt;p>Давайте смоделируем влияние изменений исходного кода на время сборки. Пусть общее время сборки равно $T_{\text{build}}$, время выполнения каждого шага — $T_{\text{layer}_i}$, а наличие попадания в кэш — логическое значение $c_i \in \{0, 1\}$ (1 при попадании в кэш).&lt;/p>
$$ T_{\text{build}} = T_{\text{init}} + \sum_{i=1}^{n} (1 - c_i) \times T_{\text{layer}_i} $$&lt;p>Как только происходит промах кэша на слое $k$ ($c_k = 0$), кэш инвалидируется ($c_j = 0$) для всех последующих слоев $j > k$.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Плохой пример (исходный код копируется первым)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>В приведенном выше случае даже изменение одной строки кода приведет к промаху кэша на первой инструкции &lt;code>COPY&lt;/code>, и каждый раз будет выполняться ресурсоемкая команда &lt;code>RUN poetry install&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Хороший пример (сначала решаются зависимости)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>При такой записи, даже при изменении исходного кода, сработает кэш слоя &lt;code>poetry install&lt;/code> ($c_i = 1$), поэтому время сборки резко сократится с нескольких минут до нескольких секунд.&lt;/p>
&lt;hr>
&lt;h2 id="9-устранение-неполадок-troubleshooting-и-советы-tips">9. Устранение неполадок (Troubleshooting) и советы (Tips)
&lt;/h2>&lt;p>Ниже приведены часто встречающиеся проблемы при работе с локальной средой и способы их решения.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Ошибка конфликта портов&lt;/strong>
Если вы получаете ошибку вроде &lt;code>Bind for 0.0.0.0:8000 failed: port is already allocated&lt;/code>, это означает, что другой процесс на локальной машине использует этот порт. Этого можно избежать, изменив номер порта на стороне хоста, например &lt;code>ports: - &amp;quot;8080:8000&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Нехватка дискового пространства&lt;/strong>
При длительном использовании Docker накапливаются неиспользуемые образы и тома (Dangling Images / Volumes), что может занять десятки гигабайт дискового пространства. Рекомендуется регулярно очищать систему с помощью следующей команды:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker system prune -a --volumes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Проблемы с правами доступа к файлам&lt;/strong>
При использовании Bind Mount в среде Linux файлы, созданные внутри контейнера, могут принадлежать пользователю &lt;code>root&lt;/code>, что сделает невозможным их редактирование на стороне хоста. Эту проблему можно решить, создав непривилегированного пользователя в Dockerfile и сопоставив его с вашим собственным UID/GID хост-ОС (например, 1000:1000).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-заключение-как-воспроизводимость-ускоряет-разработку">10. Заключение: Как воспроизводимость ускоряет разработку
&lt;/h2>&lt;p>Сочетание Docker, Docker Compose и VSCode DevContainers позволяет создать надежную локальную среду разработки, в которой «состояние будет абсолютно одинаковым независимо от того, кто ее запускает».&lt;/p>
&lt;p>Внедрение парадигмы IaC в локальную среду не просто сокращает время первоначальной настройки. Это значительно повышает скорость и качество всего цикла разработки, устраняя опасения по поводу изменений конфигурации инфраструктуры, облегчая эксперименты с новыми технологическими стеками и обеспечивая плавный переход к конвейерам (pipelines) CI/CD.&lt;/p>
&lt;p>Используя передовые методы (best practices), описанные в этой статье, такие как оптимизация размера образа с помощью многоэтапных сборок, управление зависимостями с помощью проверок работоспособности (healthcheck) и написание Dockerfile с учетом кэширования слоев, обязательно внедрите лучший опыт разработки (DX: Developer Experience) и в свои проекты.&lt;/p></description></item></channel></rss>