# Node зовётся контейнером, а не ставится на машину разработчика

- **Дата:** 2026-08-15
- **Источник:** [../../openspec/changes/archive/2026-08-15-spa-skeleton/design.md](../../openspec/changes/archive/2026-08-15-spa-skeleton/design.md),
  раздел «Node не ставится на машину, а зовётся контейнером»

## Решение

Шаг сборки приложения гоняет установщик пакетов и сборщик **внутри контейнера**,
а не вызывает их из `PATH`:

> Требованием к машине разработчика становится docker, которым и так собирается
> образ, — второго устанавливаемого окружения сверх `ffmpeg` не появляется
> вовсе.

Образ сборочного окружения берётся из ступени `Dockerfile`, а не объявляется
вторым числом в `Taskfile.yml`.

## Почему

Довод в дизайне назван прямо:

> Так снимается расхождение, которое иначе завелось бы молча: версия Node на
> машине разработчика и версия в образе — два разных числа, и собранное ими
> приложение различается ровно тогда, когда различаются они.

Отвергнуты два очевидных подхода, и оба с названной ценой. **Поставить Node на
машину** — вводит второе устанавливаемое окружение и разъезжается с версией в
образе. **Дать выбор — контейнер или локальный Node** — это второй способ делать
одно и то же, и собранное ими различалось бы в зависимости от того, у кого что
стоит.

## Почему это ADR

Запись проходит триггер **намеренным отказом** от очевидного подхода: поставить
Node на машину — ровно то, что делают по умолчанию, и отказ от этого надо
объяснить один раз, а не на каждом вопросе «почему у нас нельзя просто
`npm run build`».

## Что это меняет в прежнем решении

[ADR-2026-08-11-spa-on-vue](ADR-2026-08-11-spa-on-vue.md) записал последствием,
что «машина разработчика получает второе требуемое окружение сверх `ffmpeg`», и
подразумевал под ним Node. Окружением оказался **docker**. Сам выбор фреймворка и
наличие шага сборки это не пересматривает, поэтому статуса «заменено на» у той
записи нет: заменена не она, а толкование одного её последствия.

## Последствия

- `+` версия сборочного окружения живёт **одним** местом — ступенью
  `Dockerfile`, — и своего шага сверки ей не нужно.
- `+` собранное в наборе проверок и собранное в образе совпадает, потому что
  совпадает окружение сборки, а не потому что «обычно совпадает».
- `−` **набор проверок перестаёт работать без docker**, и отказ этот приходит
  кодом окружения. Тем же кодом приходит отказ реестра пакетов: сетезависимых
  шагов в наборе становится два вместо одного.
- `−` контейнер ходит под тем же пользователем, что и вызвавший, а кэш
  установщика уводится наружу — обе частности обязательны: без них собранное
  ляжет от `root`, а зависимости будут тянуться заново каждый прогон.
- `−` **вес и время самой ступени в образе неизвестны**: финальный образ от неё
  не растёт (ступень в рабочий слой не копируется), а время сборки решением
  владельца от 2026-08-15 не замеряется вовсе.
