<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[300MB Docker-образ на multi-stage: снос 90% легаси-депов без Vite]]></title><description><![CDATA[<p dir="auto">Ты собрал Docker-образ под <strong>300MB</strong> и чуешь, что половина - это <strong>легаси-депенденты</strong>, которые тянут за собой кучу мусора. Multi-stage билд решает это без Webpack/Vite - просто выкидываешь ненужное на этапе сборки. Получишь образ в <strong>30MB</strong>, deploy полетит быстрее, а CI/CD перестанет задыхаться.</p>
<p dir="auto">Это не магия, а базовый Docker. Зачем тащить Node.js runtime и dev-зависимости в прод? Multi-stage делит Dockerfile на этапы: builder кидает артефакты, runtime их ловит - и только голый бинарник. Размер падает в 10 раз, без костылей и новых тулов.</p>
<h2>Почему легаси-депы раздувают образ до небес</h2>
<p dir="auto">Сначала разберись, откуда жир. В типичном Node.js проекте npm install тащит <strong>200+ пакетов</strong>, половина - legacy вроде old Babel plugins или устаревших polyfill’ов. Каждый добавляет слои в образ: node_modules &gt; 100MB, плюс git history, тесты, линтеры. Docker не оптимизирует - копирует всё слоями, и итог <strong>300MB+</strong>.</p>
<p dir="auto">Берешь legacy React-проект на CRA. Там webpack, eslint, jest - все в devDependencies. docker build COPY package.json &amp;&amp; npm ci - и вуаля, пол-образа сожрано. Без multi-stage runtime STAGE наследует builder: Node 18 fat image + все депы. В проде нужен только сервер с бандлом, остальное - мертвый груз.</p>
<p dir="auto">Вот что типично раздувает:</p>
<ul>
<li><strong>node_modules</strong>: 150MB dev + prod deps.</li>
<li><strong>Git/.gitignore игнорирует не всё</strong>: тесты, coverage.</li>
<li><strong>Cache артефакты</strong>: .yarn-cache, npm-cache не чистишь - +50MB.</li>
<li><strong>Базовый образ</strong>: node:18-alpine вместо distroless - лишние утилиты.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Проблема</th>
<th>Размер вклада</th>
<th>Почему жиреет</th>
</tr>
</thead>
<tbody>
<tr>
<td>devDeps</td>
<td>120MB</td>
<td>Линтеры, тесты в runtime</td>
</tr>
<tr>
<td>Builder tools</td>
<td>80MB</td>
<td>Webpack, Babel</td>
</tr>
<tr>
<td>Cache</td>
<td>50MB</td>
<td>Не RUN rm -rf</td>
</tr>
<tr>
<td>Base img</td>
<td>50MB</td>
<td>Fat Node</td>
</tr>
</tbody>
</table>
<h2>Multi-stage: builder сносит легаси на корню</h2>
<p dir="auto">Multi-stage - это несколько FROM в Dockerfile. Первый этап <strong>builder</strong>: ставишь Node, npm ci, билдишь app. Второй <strong>runtime</strong>: копируешь только dist/ или server.js через COPY --from=builder. Всё остальное Docker выбрасывает - депы, инструменты не мигрируют.</p>
<p dir="auto">Пример: legacy Vue CLI проект. Builder: FROM node:18-alpine AS builder, COPY . ., npm ci --only=prod (dev не ставим!), npm run build. Runtime: FROM nginx:alpine, COPY --from=builder /app/dist /usr/share/nginx/html. Размер с <strong>280MB</strong> до <strong>25MB</strong>. Кэш слоев ускоряет rebuild - deps layer не пересобирается.</p>
<p dir="auto">Ключевые шаги для сноса легаси:</p>
<ol>
<li><strong>Раздели deps</strong>: npm ci --omit=dev в builder.</li>
<li><strong>Минимальный runtime</strong>: nginx:alpine или node:slim без dev.</li>
<li><strong>COPY selectively</strong>: --from=builder /app/dist только.</li>
<li><em>Нюанс</em>: алиасы AS builder для читаемости, --target для dev/prod.</li>
</ol>
<pre><code>FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci --omit=dev &amp;&amp; npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
</code></pre>
<h2>Снос 90% deps: трюки без Vite/Webpack</h2>
<p dir="auto">Легаси-проекты часто на raw webpack.config.js или parcel. Не трогай config - multi-stage снесет жир сам. Главное - билд-артефакт в /dist чистый: minify + tree-shaking вручную, если webpack старый. Добавь RUN npm prune --production в builder.</p>
<p dir="auto">Реальный кейс: Node/Express монолит с legacy deps (lodash-es, moment). Builder делает tsc + копирует server.js, runtime - FROM node:18-alpine, RUN npm ci --omit=dev --prefix=/app/server. Минус <strong>90%</strong>: с 320MB до 32MB. CI время сборки -15 мин -&gt; 3 мин.</p>
<p dir="auto">Таблица оптимизаций:</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>До</th>
<th>После</th>
<th>Что снесли</th>
</tr>
</thead>
<tbody>
<tr>
<td>300MB</td>
<td>30MB</td>
<td>devDeps 90%</td>
</tr>
<tr>
<td>15min build</td>
<td>2min</td>
<td>Кэш + prune</td>
</tr>
<tr>
<td>Fat layers</td>
<td>Lean</td>
<td>Distroless base</td>
</tr>
</tbody>
</table>
<p dir="auto">Практика:</p>
<ul>
<li><strong>Multi .dockerignore</strong>: .git, node_modules, tests/.</li>
<li><strong>Base swap</strong>: distroless/node или <a href="http://gcr.io/distroless/nodejs" target="_blank" rel="noopener noreferrer">gcr.io/distroless/nodejs</a>.</li>
<li><em>Утечка</em>: не RUN rm -rf /tmp/* после npm - +20MB.</li>
<li><strong>Verify</strong>: docker images | grep myapp, dive myapp:prod.</li>
</ul>
<h2>Бонус: когда multi-stage не панацея</h2>
<p dir="auto">Multi-stage топ для статических сайтов и Node API. Но если legacy на Go/Rust - используй scratch или musl. Для монолитов с Python - poetry export --without-hashes. Docker 25+ добавит buildx prune auto.</p>
<p dir="auto">Остается за кадром: layer caching pitfalls при COPY --from=0. Плюс security scanning в runtime. Подумай о distroless - там вообще 10MB cap. Тестируй на prod-mimic: docker save | gzip size.</p>
]]></description><link>https://forum.exlends.com/topic/2166/300mb-docker-obraz-na-multi-stage-snos-90-legasi-depov-bez-vite</link><generator>RSS for Node</generator><lastBuildDate>Thu, 23 Apr 2026 14:20:41 GMT</lastBuildDate><atom:link href="https://forum.exlends.com/topic/2166.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 22 Apr 2026 15:16:04 GMT</pubDate><ttl>60</ttl></channel></rss>