<?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>Dockerfile on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/dockerfile/</link>
    <description>Recent content in Dockerfile 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/dockerfile/feed.xml" rel="self" type="application/rss+xml" />
    <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>
  </channel>
</rss>
