Урок 03 · Build

Dockerfile для TypeScript

Соберите test runner так, чтобы изменение исходника не заставляло заново скачивать все npm-зависимости.

Время: 20-25 минутРезультат: кэшируемый imageИнтервью: слои и CMD

Предскажите кэш

Что будет пересобрано после изменения одного файла в src/?

FROM node:22-bookworm-slim
WORKDIR /app
COPY . .
RUN npm ci
CMD ["npm", "test"]

Build context и слои

Команда docker build -t wms-tests:dev . передаёт текущий каталог как build context. Инструкции Dockerfile последовательно создают кэшируемые результаты. Если входные данные слоя изменились, этот слой и следующие за ним должны выполняться заново.

Практический выводСтабильные файлы копируются раньше, часто меняющиеся — позже. Для npm сначала копируют manifests и выполняют npm ci, затем добавляют исходники.
# syntax=docker/dockerfile:1
FROM node:22-bookworm-slim AS runner

WORKDIR /app

COPY --chown=node:node package.json package-lock.json ./
RUN npm ci

COPY --chown=node:node src ./src
COPY --chown=node:node tsconfig.json ./

USER node
CMD ["npm", "test"]

RUN выполняется во время build и фиксирует результат в image. CMD задаёт команду по умолчанию для нового container и выполняется при start. Exec-form JSON помогает корректно передавать сигналы процессу.

Не отправляйте лишнее

Build context влияет на скорость, кэш и риск утечки файлов. Минимальный .dockerignore для Node-проекта:

node_modules
test-results
playwright-report
.git
.env
*.log

.dockerignore не является механизмом хранения secrets. Пароли и registry tokens не должны попадать ни в context, ни в ARG/ENV, ни в слои. Для build-time secret используйте BuildKit secret mount.

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci

docker build --secret id=npmrc,src="$HOME/.npmrc" \
  --tag wms-tests:dev .

Проверка image как артефакта

docker build --tag wms-tests:dev .
docker image inspect wms-tests:dev \
  --format 'user={{.Config.User}} cmd={{json .Config.Cmd}}'
docker history wms-tests:dev
docker run --rm wms-tests:dev
  • Build проходит из чистого context без локального node_modules.
  • Повторный build без изменений использует cache.
  • Изменение src/ не инвалидирует слой npm ci.
  • Runtime запускается не от root, если повышенные права не нужны.
  • Secrets отсутствуют в history, env и filesystem image.

Multi-stage: когда он нужен

Если TypeScript компилируется в JavaScript для production-сервиса, builder может содержать devDependencies и compiler, а финальный stage — только runtime-файлы. Для Playwright test runner финальный image всё равно содержит браузеры и test dependencies, поэтому цель multi-stage нужно формулировать конкретно, а не добавлять его автоматически.

«Кэш Dockerfile зависит от инструкции и её входов. Поэтому manifests копирую отдельно и запускаю npm ci до копирования часто меняющегося кода. RUN работает при build, а exec-form CMD задаёт основной process container. Context ограничиваю через .dockerignore, secrets передаю BuildKit mount, runtime по возможности запускаю не от root».

Проверка понимания

1. Что плохо в COPY . . перед npm ci?

2. Чем различаются RUN и CMD?

3. Как передать приватный npm token при build?

Первичные источники

Покажите преподавателю Dockerfile проекта, если порядок слоёв или runtime user требуют компромисса.