<?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>Production on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/production/</link>
    <description>Recent content in Production 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>
    <lastBuildDate>Mon, 18 May 2026 13:07:36 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/production/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>
  </channel>
</rss>
