<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Docker Levels -- от боли к пониманию on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/series/docker-levels/</link>
    <description>Recent content in Docker Levels -- от боли к пониманию on DevOps Way - Практические гайды</description>
    <image>
      <title>DevOps Way - Практические гайды</title>
      <url>https://devopsway.ru/images/devopsway-og.png</url>
      <link>https://devopsway.ru/images/devopsway-og.png</link>
    </image>
    <generator>Hugo -- 0.161.1</generator>
    <language>ru</language>
    <atom:link href="https://devopsway.ru/series/docker-levels/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Docker Level 05: Multi-stage -- Go binary 10MB, образ 900MB</title>
      <link>https://devopsway.ru/posts/docker-05-multistage/</link>
      <pubDate>Mon, 18 May 2026 14:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/docker-05-multistage/</guid>
      <description>Level 05: от 900MB до 15MB. Multi-stage build: билдер для компиляции, scratch/distroless для production. Квиз и вопрос на собеседование.</description>
      <content:encoded><![CDATA[<h2 id="боль">БОЛЬ</h2>
<p>Go-приложение. Бинарник &ndash; 10MB, статически слинкован, без зависимостей. <code>docker images</code> показывает 900MB. Внутри образа: весь Go SDK (500MB), git, gcc, make, исходники, кеш сборки. Ничего из этого не нужно в production.</p>
<p>900MB на каждый микросервис, 12 сервисов &ndash; это 10.8GB на ноду. При деплое скачивается 900MB на каждый pod restart. Rollout занимает минуты вместо секунд.</p>
<p>Multi-stage build решает проблему: один этап для сборки, другой &ndash; для запуска. В финальный образ попадает только бинарник.</p>
<h2 id="как-устроено">КАК УСТРОЕНО</h2>
<p>Multi-stage build &ndash; это несколько <code>FROM</code> в одном Dockerfile. Каждый <code>FROM</code> начинает новый этап (stage). Из предыдущего этапа можно скопировать артефакты через <code>COPY --from=</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Этап 1: builder -- полная среда сборки</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">golang:1.23</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># ... компиляция ...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Этап 2: production -- минимальный образ</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">alpine:3.20</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>builder /app/main /app/main<span class="err">
</span></span></span></code></pre></div><p>Финальный образ содержит только то, что есть в последнем <code>FROM</code> + что вы скопировали из предыдущих этапов.</p>
<p>Варианты базовых образов для production:</p>
<table>
  <thead>
      <tr>
          <th>Базовый образ</th>
          <th>Размер</th>
          <th>Когда использовать</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>scratch</code></td>
          <td>0MB</td>
          <td>Статические бинарники (Go с CGO_ENABLED=0)</td>
      </tr>
      <tr>
          <td><code>alpine</code></td>
          <td>7MB</td>
          <td>Нужен shell, CA-сертификаты, glibc</td>
      </tr>
      <tr>
          <td><code>distroless</code></td>
          <td>2&ndash;20MB</td>
          <td>Безопасность без shell</td>
      </tr>
      <tr>
          <td><code>debian-slim</code></td>
          <td>80MB</td>
          <td>Нужны системные пакеты</td>
      </tr>
  </tbody>
</table>
<h2 id="практика">ПРАКТИКА</h2>
<p>Соберите multi-stage Dockerfile для Go-приложения: этап сборки с Go SDK и финальный этап на alpine с одним бинарником.</p>
<div class="docker-sort" id="level05-quiz" data-answer="WyJGUk9NIGdvbGFuZzoxLjIzIEFTIGJ1aWxkZXIiLCJXT1JLRElSIC9hcHAiLCJDT1BZIGdvLm1vZCBnby5zdW0gLi8iLCJSVU4gZ28gbW9kIGRvd25sb2FkIiwiQ09QWSAuIC4iLCJSVU4gZ28gYnVpbGQgLW8gbWFpbiAuIiwiRlJPTSBhbHBpbmU6My4yMCIsIldPUktESVIgL2FwcCIsIkNPUFkgLS1mcm9tPWJ1aWxkZXIgL2FwcC9tYWluIC9hcHAvbWFpbiIsIkNNRCBbXCIvYXBwL21haW5cIl0iXQ==">
  <p class="docker-sort__title">Расставьте multi-stage Dockerfile в правильном порядке</p>
  <ul class="docker-sort__list"><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">CMD [&#34;/app/main&#34;]</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY --from=builder /app/main /app/main</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">RUN go build -o main .</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">FROM alpine:3.20</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY go.mod go.sum ./</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">WORKDIR /app</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">RUN go mod download</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">FROM golang:1.23 AS builder</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY . .</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">WORKDIR /app</span>
    </li></ul>
  <div class="docker-sort__actions">
    <button class="docker-sort__btn docker-sort__btn--check" type="button">Проверить</button>
    <button class="docker-sort__btn docker-sort__btn--reset" type="button">Сбросить</button>
  </div>
  <div class="docker-sort__result"></div><div class="docker-sort__explain">Два этапа: (1) <code>builder</code> &ndash; Go SDK, скачивание зависимостей (кешируется через <code>go.mod</code>/<code>go.sum</code>), компиляция; (2) production &ndash; чистый alpine, копируем только бинарник через <code>COPY --from=builder</code>. Финальный образ не содержит Go SDK, исходников и кеша сборки.</div></div>

<h2 id="разбор">РАЗБОР</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># ===== Этап 1: Builder =====</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Полный Go SDK для компиляции</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">golang:1.23</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Сначала зависимости (кешируются)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> go.mod go.sum ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> go mod download<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Затем исходники</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Компиляция статического бинарника</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> go build -o main .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># ===== Этап 2: Production =====</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Минимальный образ -- только для запуска</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">alpine:3.20</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Копируем ТОЛЬКО бинарник из builder</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>builder /app/main /app/main<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Запуск</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;/app/main&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Результат:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker images
</span></span><span class="line"><span class="cl"><span class="c1"># REPOSITORY  TAG     SIZE</span>
</span></span><span class="line"><span class="cl"><span class="c1"># myapp       latest  17MB    # &lt;- вместо 900MB</span>
</span></span></code></pre></div><p>900MB -&gt; 17MB. Разница в 50 раз. Rollout быстрее, registry меньше, attack surface минимален.</p>
<p>Для ещё меньшего образа &ndash; <code>scratch</code> вместо <code>alpine</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">scratch</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>builder /app/main /app/main<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;/app/main&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Это даст ~12MB, но без shell &ndash; нельзя зайти внутрь для отладки.</p>
<h2 id="вопрос-на-собесе">ВОПРОС НА СОБЕСЕ</h2>
<p><strong>Вопрос:</strong> Как уменьшить размер Docker-образа? Назовите 3&ndash;5 способов.</p>
<details class="expand-container expand-default ">
    <summary class="expand-header">
        <span class="expand-icon">▶</span>
        <span class="expand-title">Показать ответ</span>
        <span class="expand-chevron">⌄</span>
    </summary>
    <div class="expand-content">
        <ol>
<li>
<p><strong>Multi-stage build</strong> &ndash; отделить среду сборки от runtime. Самый значительный эффект: сотни мегабайт -&gt; десятки.</p>
</li>
<li>
<p><strong>Маленький базовый образ</strong> &ndash; <code>alpine</code> (7MB) вместо <code>ubuntu</code> (78MB), <code>distroless</code> (2&ndash;20MB) вместо <code>debian</code>, <code>scratch</code> (0MB) для статических бинарников.</p>
</li>
<li>
<p><strong><code>.dockerignore</code></strong> &ndash; исключить <code>.git</code>, <code>node_modules</code>, тесты, документацию. Уменьшает и контекст, и образ.</p>
</li>
<li>
<p><strong>Объединение RUN-инструкций</strong> &ndash; каждый <code>RUN</code> создаёт слой. <code>RUN apt-get update &amp;&amp; apt-get install -y curl &amp;&amp; rm -rf /var/lib/apt/lists/*</code> &ndash; установка и очистка в одном слое.</p>
</li>
<li>
<p><strong>Минимальные зависимости</strong> &ndash; <code>apt-get install --no-install-recommends</code>, <code>npm ci --production</code>, <code>go build</code> с <code>-ldflags=&quot;-s -w&quot;</code> (strip debug info).</p>
</li>
</ol>
<p>Бонус: <code>docker-slim</code> / <code>docker scout</code> для анализа и автоматического уменьшения образов.</p>

    </div>
</details>

]]></content:encoded>
    </item>
    <item>
      <title>Docker Level 04: .dockerignore -- Образ 2GB, внутри .env с паролями</title>
      <link>https://devopsway.ru/posts/docker-04-dockerignore/</link>
      <pubDate>Mon, 18 May 2026 13:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/docker-04-dockerignore/</guid>
      <description>Level 04: почему образ весит 2GB и содержит .env с паролями. Как .dockerignore защищает от утечек и ускоряет сборку.</description>
      <content:encoded><![CDATA[<h2 id="боль">БОЛЬ</h2>
<p>Ревью Docker-образа перед деплоем. <code>docker images</code> показывает 2.1GB для простого Node.js-приложения. Запускаете <code>docker run --rm myapp ls -la</code> и видите: <code>node_modules</code> (800MB), <code>.git</code> (400MB), <code>test-data</code> (500MB). И самое страшное &ndash; <code>.env</code> с <code>DB_PASSWORD</code>, <code>JWT_SECRET</code>, <code>AWS_SECRET_KEY</code>.</p>
<p>Образ уже в Docker Hub. Пароли в открытом доступе. Даже если удалить <code>.env</code> в следующем слое &ndash; он останется в истории образа. <code>docker history</code> покажет всё.</p>
<p>Исправление: <strong><code>.dockerignore</code></strong> &ndash; файл-фильтр, который не пускает лишнее в контекст сборки.</p>
<h2 id="как-устроено">КАК УСТРОЕНО</h2>
<p>Когда вы запускаете <code>docker build .</code>, Docker отправляет в daemon весь текущий каталог &ndash; это <strong>build context</strong>. Всё, что в контексте &ndash; доступно для <code>COPY</code> и <code>ADD</code>. Всё, что не нужно &ndash; замедляет сборку и раздувает образ.</p>
<p><code>.dockerignore</code> работает как <code>.gitignore</code>: указывает файлы и директории, которые НЕ попадут в build context.</p>
<p>Что должно быть в <code>.dockerignore</code> почти всегда:</p>
<table>
  <thead>
      <tr>
          <th>Паттерн</th>
          <th>Зачем</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.git</code></td>
          <td>История репозитория, сотни мегабайт</td>
      </tr>
      <tr>
          <td><code>node_modules</code></td>
          <td>Зависимости &ndash; ставятся при сборке через <code>npm ci</code></td>
      </tr>
      <tr>
          <td><code>.env</code></td>
          <td>Секреты, пароли, токены</td>
      </tr>
      <tr>
          <td><code>*.log</code></td>
          <td>Логи &ndash; не нужны в образе</td>
      </tr>
      <tr>
          <td><code>Dockerfile</code></td>
          <td>Сам Dockerfile не нужен внутри</td>
      </tr>
      <tr>
          <td><code>.dockerignore</code></td>
          <td>Мета-файл</td>
      </tr>
      <tr>
          <td><code>tests/</code> / <code>__tests__/</code></td>
          <td>Тесты &ndash; не нужны в production</td>
      </tr>
      <tr>
          <td><code>*.md</code></td>
          <td>Документация</td>
      </tr>
      <tr>
          <td><code>.vscode</code> / <code>.idea</code></td>
          <td>Настройки редактора</td>
      </tr>
  </tbody>
</table>
<h2 id="практика">ПРАКТИКА</h2>
<p>Составьте правильный <code>.dockerignore</code> для Node.js-проекта. Расставьте строки в логичном порядке: сначала VCS и среда, затем зависимости, секреты, тесты и документация.</p>
<div class="docker-sort" id="level04-quiz" data-answer="WyIuZ2l0IiwiLmdpdGlnbm9yZSIsIi52c2NvZGUiLCIuaWRlYSIsIm5vZGVfbW9kdWxlcyIsIi5lbnYiLCIuZW52LioiLCJEb2NrZXJmaWxlIiwiLmRvY2tlcmlnbm9yZSIsInRlc3RzLyIsImNvdmVyYWdlLyIsIioubG9nIiwiKi5tZCJd">
  <p class="docker-sort__title">Расставьте .dockerignore в правильном порядке</p>
  <ul class="docker-sort__list"><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">*.md</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.env</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.env.*</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">node_modules</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.git</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.gitignore</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">Dockerfile</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.dockerignore</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">tests/</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">coverage/</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">*.log</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.vscode</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">.idea</span>
    </li></ul>
  <div class="docker-sort__actions">
    <button class="docker-sort__btn docker-sort__btn--check" type="button">Проверить</button>
    <button class="docker-sort__btn docker-sort__btn--reset" type="button">Сбросить</button>
  </div>
  <div class="docker-sort__result"></div><div class="docker-sort__explain">Логичная группировка: (1) VCS и редактор &ndash; <code>.git</code>, <code>.gitignore</code>, <code>.vscode</code>, <code>.idea</code>; (2) зависимости &ndash; <code>node_modules</code>; (3) секреты &ndash; <code>.env</code>, <code>.env.*</code>; (4) мета-файлы Docker &ndash; <code>Dockerfile</code>, <code>.dockerignore</code>; (5) тесты и артефакты &ndash; <code>tests/</code>, <code>coverage/</code>, <code>*.log</code>; (6) документация &ndash; <code>*.md</code>. Порядок внутри <code>.dockerignore</code> технически не важен, но группировка помогает при ревью.</div></div>

<h2 id="разбор">РАЗБОР</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># .dockerignore
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># VCS и настройки редактора
</span></span><span class="line"><span class="cl">.git
</span></span><span class="line"><span class="cl">.gitignore
</span></span><span class="line"><span class="cl">.vscode
</span></span><span class="line"><span class="cl">.idea
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Зависимости (ставятся при сборке)
</span></span><span class="line"><span class="cl">node_modules
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Секреты — НИКОГДА не должны попасть в образ
</span></span><span class="line"><span class="cl">.env
</span></span><span class="line"><span class="cl">.env.*
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Docker мета-файлы
</span></span><span class="line"><span class="cl">Dockerfile
</span></span><span class="line"><span class="cl">.dockerignore
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Тесты и покрытие
</span></span><span class="line"><span class="cl">tests/
</span></span><span class="line"><span class="cl">coverage/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Логи и документация
</span></span><span class="line"><span class="cl">*.log
</span></span><span class="line"><span class="cl">*.md
</span></span></code></pre></div><p>Эффект:</p>
<ul>
<li><strong>Размер</strong>: контекст сборки уменьшается с 2GB до десятков мегабайт</li>
<li><strong>Скорость</strong>: <code>docker build</code> не копирует гигабайты в daemon</li>
<li><strong>Безопасность</strong>: <code>.env</code> физически не попадает в образ, даже случайный <code>COPY . .</code> не утянет секреты</li>
</ul>
<p>Проверить что попадает в контекст:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Размер контекста видно в первой строке docker build</span>
</span></span><span class="line"><span class="cl">docker build . 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; Sending build context to Docker daemon  45.2MB</span>
</span></span></code></pre></div><h2 id="вопрос-на-собесе">ВОПРОС НА СОБЕСЕ</h2>
<p><strong>Вопрос:</strong> Секрет попал в Docker-образ через <code>COPY</code>. Удаление файла в следующем слое решает проблему?</p>
<details class="expand-container expand-default ">
    <summary class="expand-header">
        <span class="expand-icon">▶</span>
        <span class="expand-title">Показать ответ</span>
        <span class="expand-chevron">⌄</span>
    </summary>
    <div class="expand-content">
        <p>Нет. Docker-образ состоит из слоёв, и каждый слой иммутабелен. Если в слое 3 скопировали <code>.env</code>, а в слое 4 удалили &ndash; файл всё ещё есть в слое 3. Любой, кто скачает образ, может извлечь его:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Посмотреть историю слоёв</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">history</span> myapp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Извлечь файловую систему конкретного слоя</span>
</span></span><span class="line"><span class="cl">docker save myapp <span class="p">|</span> tar -xf - --include<span class="o">=</span><span class="s1">&#39;*/layer.tar&#39;</span>
</span></span></code></pre></div><p>Правильные способы работы с секретами:</p>
<ol>
<li><strong><code>.dockerignore</code></strong> &ndash; секреты не попадают в контекст сборки</li>
<li><strong>BuildKit secrets</strong> &ndash; <code>docker build --secret id=mysecret,src=.env</code> + <code>RUN --mount=type=secret,id=mysecret</code></li>
<li><strong>Переменные окружения</strong> &ndash; передаются при <code>docker run -e</code>, не запекаются в образ</li>
<li><strong>Vault / Secrets Manager</strong> &ndash; приложение получает секреты в runtime</li>
</ol>
<p>Если секрет уже попал в образ &ndash; пересоберите с нуля (<code>docker build --no-cache</code>) и отзовите скомпрометированные credentials.</p>

    </div>
</details>

]]></content:encoded>
    </item>
    <item>
      <title>Docker Level 03: Layer Cache -- Почему сборка 8 минут?</title>
      <link>https://devopsway.ru/posts/docker-03-layer-cache/</link>
      <pubDate>Mon, 18 May 2026 12:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/docker-03-layer-cache/</guid>
      <description>Level 03: почему CI build занимает 8 минут из-за одной строки. Механика слоёв Docker, кеширование и правильный порядок COPY/RUN.</description>
      <content:encoded><![CDATA[<h2 id="боль">БОЛЬ</h2>
<p>CI pipeline: 47 коммитов в день, каждый запускает <code>docker build</code>. Сборка занимает 8 минут, из них 7 &ndash; <code>npm ci</code>. Вы поменяли одну строку в <code>src/index.ts</code>, но Docker заново устанавливает все 1200 зависимостей. Каждый. Раз.</p>
<p>За день это 47 * 7 = <strong>329 минут впустую</strong>. За месяц &ndash; 110 часов ожидания. И всё потому, что <code>COPY . .</code> стоит перед <code>RUN npm ci</code>.</p>
<h2 id="как-устроено">КАК УСТРОЕНО</h2>
<p>Docker собирает образ слоями. Каждая инструкция <code>FROM</code>, <code>COPY</code>, <code>RUN</code> создаёт слой. Слои кешируются.</p>
<p>Правило кеша: если инструкция и всё, что было до неё, не менялись &ndash; Docker берёт слой из кеша. Но если слой инвалидирован &ndash; <strong>все последующие слои тоже пересобираются</strong>.</p>
<p>Плохой порядок:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">COPY</span> . .          <span class="c1"># &lt;-- любое изменение файла инвалидирует кеш</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm ci        <span class="c1"># &lt;-- пересобирается каждый раз</span><span class="err">
</span></span></span></code></pre></div><p>Хороший порядок:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">COPY</span> package.json package-lock.json ./   <span class="c1"># &lt;-- меняется редко</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm ci                                <span class="c1"># &lt;-- кешируется!</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .                                  <span class="c1"># &lt;-- только исходники</span><span class="err">
</span></span></span></code></pre></div><p>Принцип: <strong>от редко меняющегося к часто меняющемуся</strong>. Зависимости меняются раз в неделю, код &ndash; 47 раз в день. Разнесите их по разным слоям.</p>
<h2 id="практика">ПРАКТИКА</h2>
<p>Dockerfile для Node.js-приложения тормозит CI. Расставьте инструкции так, чтобы <code>npm ci</code> кешировался при изменении исходников.</p>
<div class="docker-sort" id="level03-quiz" data-answer="WyJGUk9NIG5vZGU6MjItYWxwaW5lIiwiV09SS0RJUiAvYXBwIiwiQ09QWSBwYWNrYWdlLmpzb24gcGFja2FnZS1sb2NrLmpzb24gLi8iLCJSVU4gbnBtIGNpIiwiQ09QWSAuIC4iLCJSVU4gbnBtIHJ1biBidWlsZCIsIkNNRCBbXCJub2RlXCIsIFwiZGlzdC9pbmRleC5qc1wiXSJd">
  <p class="docker-sort__title">Расставьте Dockerfile для максимального кеша</p>
  <ul class="docker-sort__list"><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY . .</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">RUN npm ci</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY package.json package-lock.json ./</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">FROM node:22-alpine</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">WORKDIR /app</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">RUN npm run build</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">CMD [&#34;node&#34;, &#34;dist/index.js&#34;]</span>
    </li></ul>
  <div class="docker-sort__actions">
    <button class="docker-sort__btn docker-sort__btn--check" type="button">Проверить</button>
    <button class="docker-sort__btn docker-sort__btn--reset" type="button">Сбросить</button>
  </div>
  <div class="docker-sort__result"></div><div class="docker-sort__explain">Сначала <code>package.json</code> и <code>package-lock.json</code> &ndash; они меняются редко. <code>npm ci</code> устанавливает зависимости и кешируется, пока lock-файл не изменился. <code>COPY . .</code> копирует исходники &ndash; они меняются часто, но инвалидируют только слои ниже (build, CMD), а не npm ci.</div></div>

<h2 id="разбор">РАЗБОР</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># 1. Базовый образ -- меняется раз в квартал</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">node:22-alpine</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 2. Рабочая директория -- никогда не меняется</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 3. Lock-файлы -- меняются раз в неделю</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> package.json package-lock.json ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 4. Установка зависимостей -- кешируется пока lock-файл тот же</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm ci<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 5. Исходники -- меняются каждый коммит</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 6. Сборка -- пересобирается при изменении кода (это ок)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm run build<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># 7. Запуск</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;node&#34;</span><span class="p">,</span> <span class="s2">&#34;dist/index.js&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Результат: при изменении кода Docker использует кеш для шагов 1&ndash;4 (FROM, WORKDIR, COPY lock-файлов, npm ci) и пересобирает только шаги 5&ndash;7. Вместо 8 минут &ndash; 30 секунд.</p>
<p>Проверить кеширование можно в выводе <code>docker build</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">=&gt; CACHED [2/6] WORKDIR /app
</span></span><span class="line"><span class="cl">=&gt; CACHED [3/6] COPY package.json package-lock.json ./
</span></span><span class="line"><span class="cl">=&gt; CACHED [4/6] RUN npm ci
</span></span><span class="line"><span class="cl">=&gt; [5/6] COPY . .
</span></span></code></pre></div><p><code>CACHED</code> &ndash; значит слой взят из кеша.</p>
<h2 id="вопрос-на-собесе">ВОПРОС НА СОБЕСЕ</h2>
<p><strong>Вопрос:</strong> Как работает кеширование слоёв Docker и когда кеш инвалидируется?</p>
<details class="expand-container expand-default ">
    <summary class="expand-header">
        <span class="expand-icon">▶</span>
        <span class="expand-title">Показать ответ</span>
        <span class="expand-chevron">⌄</span>
    </summary>
    <div class="expand-content">
        <p>Docker кеширует каждый слой (результат инструкции). При повторной сборке проверяет:</p>
<ol>
<li><strong>FROM</strong>: совпадает ли базовый образ (тег + digest)</li>
<li><strong>RUN</strong>: совпадает ли текст команды (посимвольно)</li>
<li><strong>COPY/ADD</strong>: совпадают ли checksum&rsquo;ы копируемых файлов</li>
</ol>
<p>Если на каком-то шаге кеш инвалидирован &ndash; <strong>все последующие шаги пересобираются</strong>, даже если сами не менялись. Это называется &ldquo;cache bust&rdquo;.</p>
<p>Типичные причины инвалидации:</p>
<ul>
<li><code>COPY . .</code> перед <code>RUN npm install</code> &ndash; любой файл в контексте инвалидирует кеш</li>
<li><code>RUN apt-get update &amp;&amp; apt-get install -y curl</code> без <code>--no-cache</code> &ndash; текст не менялся, кеш работает, но пакеты могут устареть</li>
<li><code>ARG VERSION=1.0</code> &ndash; изменение ARG инвалидирует все слои после его использования</li>
</ul>
<p>Лучшие практики: сортировать инструкции от редко меняющихся к часто меняющимся. Lock-файлы отдельно от исходников. Объединять RUN-команды с <code>&amp;&amp;</code> чтобы уменьшить количество слоёв.</p>

    </div>
</details>

]]></content:encoded>
    </item>
    <item>
      <title>Docker Level 02: Dockerfile -- Работает у меня, а у коллеги нет</title>
      <link>https://devopsway.ru/posts/docker-02-dockerfile/</link>
      <pubDate>Mon, 18 May 2026 11:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/docker-02-dockerfile/</guid>
      <description>Level 02: от &amp;#39;работает у меня&amp;#39; до воспроизводимой сборки через Dockerfile. Квиз по порядку инструкций и вопрос на собеседование.</description>
      <content:encoded><![CDATA[<h2 id="боль">БОЛЬ</h2>
<p>Джуниор присылает в чат: &ldquo;У меня всё работает&rdquo;. Вы клонируете репозиторий, запускаете <code>npm install</code> &ndash; ошибка. У него Node 18, у вас Node 22. У него Ubuntu, у вас macOS. У него <code>python3</code> указывает на 3.10, у вас &ndash; на 3.12.</p>
<p>&ldquo;Работает у меня&rdquo; &ndash; это не баг, это отсутствие воспроизводимой среды. Dockerfile решает проблему: он описывает среду декларативно. Кто бы ни собрал образ &ndash; результат будет одинаковый.</p>
<h2 id="как-устроено">КАК УСТРОЕНО</h2>
<p>Dockerfile &ndash; это текстовый файл с инструкциями для сборки образа. Каждая инструкция создаёт слой (layer) в образе.</p>
<p>Основные инструкции:</p>
<table>
  <thead>
      <tr>
          <th>Инструкция</th>
          <th>Назначение</th>
          <th>Пример</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>FROM</code></td>
          <td>Базовый образ</td>
          <td><code>FROM node:22-alpine</code></td>
      </tr>
      <tr>
          <td><code>WORKDIR</code></td>
          <td>Рабочая директория</td>
          <td><code>WORKDIR /app</code></td>
      </tr>
      <tr>
          <td><code>COPY</code></td>
          <td>Копировать файлы из контекста</td>
          <td><code>COPY . .</code></td>
      </tr>
      <tr>
          <td><code>RUN</code></td>
          <td>Выполнить команду при сборке</td>
          <td><code>RUN npm install</code></td>
      </tr>
      <tr>
          <td><code>ENV</code></td>
          <td>Переменная окружения</td>
          <td><code>ENV NODE_ENV=production</code></td>
      </tr>
      <tr>
          <td><code>EXPOSE</code></td>
          <td>Документация порта</td>
          <td><code>EXPOSE 3000</code></td>
      </tr>
      <tr>
          <td><code>CMD</code></td>
          <td>Команда запуска контейнера</td>
          <td><code>CMD [&quot;node&quot;, &quot;app.js&quot;]</code></td>
      </tr>
  </tbody>
</table>
<p>Порядок инструкций важен: Docker кеширует слои, и если слой не менялся &ndash; он берётся из кеша. Об этом подробнее в Level 03.</p>
<h2 id="практика">ПРАКТИКА</h2>
<p>Вам нужно собрать образ для Node.js-приложения: взять базовый образ, задать рабочую директорию, скопировать исходники, установить зависимости, указать порт и команду запуска.</p>
<div class="docker-sort" id="level02-quiz" data-answer="WyJGUk9NIG5vZGU6MjItYWxwaW5lIiwiV09SS0RJUiAvYXBwIiwiQ09QWSAuIC4iLCJSVU4gbnBtIGluc3RhbGwiLCJFWFBPU0UgMzAwMCIsIkNNRCBbXCJub2RlXCIsIFwiYXBwLmpzXCJdIl0=">
  <p class="docker-sort__title">Расставьте Dockerfile в правильном порядке</p>
  <ul class="docker-sort__list"><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">CMD [&#34;node&#34;, &#34;app.js&#34;]</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">COPY . .</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">RUN npm install</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">EXPOSE 3000</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">FROM node:22-alpine</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">WORKDIR /app</span>
    </li></ul>
  <div class="docker-sort__actions">
    <button class="docker-sort__btn docker-sort__btn--check" type="button">Проверить</button>
    <button class="docker-sort__btn docker-sort__btn--reset" type="button">Сбросить</button>
  </div>
  <div class="docker-sort__result"></div><div class="docker-sort__explain"><code>FROM</code> всегда первая инструкция &ndash; определяет базовый образ. <code>WORKDIR</code> задаёт директорию для всех последующих команд. <code>COPY</code> копирует исходники, <code>RUN</code> устанавливает зависимости. <code>EXPOSE</code> документирует порт (не открывает его!). <code>CMD</code> &ndash; команда при запуске контейнера, всегда последняя.</div></div>

<h2 id="разбор">РАЗБОР</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Базовый образ: Node.js 22 на Alpine Linux (маленький)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">node:22-alpine</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Все команды выполняются в /app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Копируем всё из текущей директории в /app контейнера</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Устанавливаем зависимости</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm install<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Документируем порт (для docker run -P)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">3000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Команда запуска -- выполнится при docker run</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;node&#34;</span><span class="p">,</span> <span class="s2">&#34;app.js&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Этот Dockerfile работает, но не оптимален. Каждое изменение кода инвалидирует кеш <code>npm install</code>. Как это исправить &ndash; в Level 03.</p>
<p>Сборка и запуск:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t myapp .
</span></span><span class="line"><span class="cl">docker run -d -p 3000:3000 myapp
</span></span></code></pre></div><h2 id="вопрос-на-собесе">ВОПРОС НА СОБЕСЕ</h2>
<p><strong>Вопрос:</strong> В чём разница между <code>CMD</code> и <code>ENTRYPOINT</code>?</p>
<details class="expand-container expand-default ">
    <summary class="expand-header">
        <span class="expand-icon">▶</span>
        <span class="expand-title">Показать ответ</span>
        <span class="expand-chevron">⌄</span>
    </summary>
    <div class="expand-content">
        <p><code>CMD</code> задаёт команду по умолчанию, которую можно полностью переопределить при <code>docker run</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;node&#34;</span><span class="p">,</span> <span class="s2">&#34;app.js&#34;</span><span class="p">]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># docker run myapp           -&gt; node app.js</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># docker run myapp bash      -&gt; bash (CMD заменён)</span><span class="err">
</span></span></span></code></pre></div><p><code>ENTRYPOINT</code> задаёт фиксированную команду, а аргументы из <code>docker run</code> дописываются к ней:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">ENTRYPOINT</span> <span class="p">[</span><span class="s2">&#34;node&#34;</span><span class="p">]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;app.js&#34;</span><span class="p">]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># docker run myapp           -&gt; node app.js</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># docker run myapp server.js -&gt; node server.js (CMD заменён, ENTRYPOINT нет)</span><span class="err">
</span></span></span></code></pre></div><p>На практике: <code>CMD</code> &ndash; для приложений, где пользователь может захотеть запустить shell. <code>ENTRYPOINT</code> &ndash; для утилит, где команда фиксирована (например, <code>curl</code>, <code>psql</code>). Комбинация <code>ENTRYPOINT</code> + <code>CMD</code> даёт фиксированную команду с дефолтными аргументами.</p>

    </div>
</details>

]]></content:encoded>
    </item>
    <item>
      <title>Docker Level 01: docker run -- Зачем вообще контейнеры?</title>
      <link>https://devopsway.ru/posts/docker-01-run/</link>
      <pubDate>Mon, 18 May 2026 10:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/docker-01-run/</guid>
      <description>Level 01: от 5 сервисов на голом хосте до изолированных контейнеров. Интерактивный квиз по флагам docker run и вопрос на собеседование.</description>
      <content:encoded><![CDATA[<h2 id="боль">БОЛЬ</h2>
<p>Пятница, 18:47. Вы обновляете Python на сервере, где крутятся 5 микросервисов. <code>apt upgrade python3</code> &ndash; и бот в Telegram падает, потому что ему нужен Python 3.10, а вы только что поставили 3.12. Заодно сломался Flask-сервис, который зависел от <code>libssl1.1</code>, а теперь стоит <code>libssl3</code>.</p>
<p>Знакомо? Это классика: <strong>конфликт зависимостей на общем хосте</strong>. Один <code>apt update</code> может положить всё, потому что 5 сервисов делят одну файловую систему, одни библиотеки, один Python.</p>
<p>Docker решает эту боль изоляцией: каждый сервис живёт в своём контейнере со своими зависимостями. Обновление Python в одном контейнере не трогает остальные.</p>
<h2 id="как-устроено">КАК УСТРОЕНО</h2>
<p><code>docker run</code> &ndash; это точка входа в мир контейнеров. Одна команда берёт образ (image) и запускает из него изолированный процесс (контейнер).</p>
<p>Что происходит при <code>docker run nginx</code>:</p>
<ol>
<li>Docker ищет образ <code>nginx</code> локально</li>
<li>Если нет &ndash; скачивает из Docker Hub</li>
<li>Создаёт изолированный контейнер (namespaces + cgroups)</li>
<li>Запускает процесс nginx внутри</li>
</ol>
<p>Ключевые флаги, которые нужно знать:</p>
<table>
  <thead>
      <tr>
          <th>Флаг</th>
          <th>Что делает</th>
          <th>Пример</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>-d</code></td>
          <td>Фоновый режим (detach)</td>
          <td><code>docker run -d nginx</code></td>
      </tr>
      <tr>
          <td><code>-p</code></td>
          <td>Проброс порта host:container</td>
          <td><code>-p 8080:80</code></td>
      </tr>
      <tr>
          <td><code>--name</code></td>
          <td>Имя контейнера</td>
          <td><code>--name web</code></td>
      </tr>
      <tr>
          <td><code>-e</code></td>
          <td>Переменная окружения</td>
          <td><code>-e DB_HOST=postgres</code></td>
      </tr>
      <tr>
          <td><code>-v</code></td>
          <td>Том (volume)</td>
          <td><code>-v ./data:/app/data</code></td>
      </tr>
      <tr>
          <td><code>--rm</code></td>
          <td>Удалить после остановки</td>
          <td><code>docker run --rm alpine echo hi</code></td>
      </tr>
      <tr>
          <td><code>--network</code></td>
          <td>Сеть</td>
          <td><code>--network backend</code></td>
      </tr>
  </tbody>
</table>
<h2 id="практика">ПРАКТИКА</h2>
<p>Вам нужно запустить веб-приложение в контейнере: фоновый режим, проброс порта 8080 на 80, имя <code>webapp</code>, переменная <code>NODE_ENV=production</code>, том для данных.</p>
<div class="docker-sort" id="level01-quiz" data-answer="WyJkb2NrZXIgcnVuIC1kIiwiLS1uYW1lIHdlYmFwcCIsIi1wIDgwODA6ODAiLCItZSBOT0RFX0VOVj1wcm9kdWN0aW9uIiwiLXYgLi9kYXRhOi9hcHAvZGF0YSIsIm5vZGU6MjItYWxwaW5lIl0=">
  <p class="docker-sort__title">Расставьте части команды docker run в правильном порядке</p>
  <ul class="docker-sort__list"><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">--name webapp</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">-v ./data:/app/data</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">-e NODE_ENV=production</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">node:22-alpine</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">-p 8080:80</span>
    </li><li class="docker-sort__item">
      <span class="docker-sort__grip">&#10303;</span>
      <span class="docker-sort__text">docker run -d</span>
    </li></ul>
  <div class="docker-sort__actions">
    <button class="docker-sort__btn docker-sort__btn--check" type="button">Проверить</button>
    <button class="docker-sort__btn docker-sort__btn--reset" type="button">Сбросить</button>
  </div>
  <div class="docker-sort__result"></div><div class="docker-sort__explain">Порядок флагов docker run: сначала команда <code>docker run -d</code>, затем все флаги (<code>--name</code>, <code>-p</code>, <code>-e</code>, <code>-v</code>), и в самом конце &ndash; имя образа. Образ всегда идёт последним, после него можно указать только команду для запуска внутри контейнера. Порядок флагов между собой не критичен, но образ обязательно в конце.</div></div>

<h2 id="разбор">РАЗБОР</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Полная команда</span>
</span></span><span class="line"><span class="cl">docker run -d <span class="se">\
</span></span></span><span class="line"><span class="cl">  --name webapp <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p 8080:80 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">NODE_ENV</span><span class="o">=</span>production <span class="se">\
</span></span></span><span class="line"><span class="cl">  -v ./data:/app/data <span class="se">\
</span></span></span><span class="line"><span class="cl">  node:22-alpine
</span></span></code></pre></div><p>Что здесь происходит:</p>
<ul>
<li><code>docker run -d</code> &ndash; запускаем контейнер в фоне, терминал не блокируется</li>
<li><code>--name webapp</code> &ndash; даём контейнеру понятное имя вместо случайного <code>angry_turing</code></li>
<li><code>-p 8080:80</code> &ndash; порт 8080 на хосте проксируется на порт 80 контейнера</li>
<li><code>-e NODE_ENV=production</code> &ndash; переменная окружения, приложение знает что это прод</li>
<li><code>-v ./data:/app/data</code> &ndash; локальная папка <code>./data</code> монтируется внутрь контейнера, данные переживут рестарт</li>
<li><code>node:22-alpine</code> &ndash; образ (всегда последний аргумент перед командой)</li>
</ul>
<h2 id="вопрос-на-собесе">ВОПРОС НА СОБЕСЕ</h2>
<p><strong>Вопрос:</strong> Чем отличается контейнер от виртуальной машины?</p>
<details class="expand-container expand-default ">
    <summary class="expand-header">
        <span class="expand-icon">▶</span>
        <span class="expand-title">Показать ответ</span>
        <span class="expand-chevron">⌄</span>
    </summary>
    <div class="expand-content">
        <p>Контейнер &ndash; это изолированный процесс, который делит ядро ОС с хостом. VM &ndash; это полная эмуляция железа со своим ядром.</p>
<p>Практические отличия:</p>
<ul>
<li><strong>Запуск</strong>: контейнер стартует за миллисекунды, VM &ndash; за минуты</li>
<li><strong>Размер</strong>: контейнер &ndash; мегабайты, VM &ndash; гигабайты</li>
<li><strong>Изоляция</strong>: VM сильнее (своё ядро), контейнер &ndash; слабее (общее ядро)</li>
<li><strong>Плотность</strong>: на одном хосте могут работать сотни контейнеров, но только десятки VM</li>
</ul>
<p>Docker использует namespaces (изоляция PID, сети, файловой системы) и cgroups (лимиты CPU, RAM). Это не эмуляция &ndash; это ограничение видимости процесса в существующем ядре.</p>

    </div>
</details>

]]></content:encoded>
    </item>
  </channel>
</rss>
