Создатель Docker контейнеров
Создаёт оптимизированный Dockerfile с multi-stage сборкой, кэшированием, безопасностью и документацией для вашего приложения.
Пример запуска
Сгенерировано Claude Opus 5.5, 24 сентября 2026 г.
Значения переменных
- Название приложения
- shiplog-api
- Язык и фреймворк
- Node.js 20 (TypeScript) + Express.js
- Менеджер зависимостей
- pnpm 9
- Команда сборки
- pnpm install --frozen-lockfile && pnpm run build
- Команда запуска
- node dist/server.js
- Порт
- 4000
- Окружение, например production
- production (Kubernetes-кластер в Yandex Cloud, AMD64)
Production-ready Dockerfile для shiplog-api
⚠️ Node.js 20 перестал поддерживаться 30 апреля 2026. Сейчас сентябрь 2026, поэтому версия 20 больше не получает патчей безопасности. Dockerfile ниже собран под Node 20, как вы просили, но версия вынесена в
ARG NODE_VERSION. Рекомендую перейти на Node 22 (maintenance LTS до апреля 2027) или Node 24 (active LTS). Для этого достаточно поменять одно значение и прогнать тесты.
Допущения (уточняющие вопросы в конце; если что-то не так, Dockerfile легко поправить):
- приложение читает порт из
process.env.PORT; - есть эндпоинт
GET /healthz; - это не монорепо: один
package.jsonиpnpm-lock.yamlв корне; tscсобирает всё вdist/, а в рантайме не нужны файлы внеdist/(миграции, шаблоны).
1. Dockerfile
# syntax=docker/dockerfile:1.7
# ↑ Включает BuildKit-фичи: cache mounts, secret mounts, heredoc.
############################################################
# Глобальные аргументы: версии в одном месте
############################################################
ARG NODE_VERSION=20
ARG DEBIAN_RELEASE=bookworm
ARG PNPM_VERSION=9.15.9
############################################################
# Stage 0: base, общий фундамент для сборочных stage'ей
############################################################
FROM node:${NODE_VERSION}-${DEBIAN_RELEASE}-slim AS base
# slim-вариант Debian: glibc (совместим с prebuilt-бинарниками нативных модулей)
# и при этом без компиляторов, man-страниц и прочего балласта.
ARG PNPM_VERSION
# Ставим pnpm через npm, а не через corepack: corepack убран из Node 25+
# и в старых патч-версиях Node падает на проверке подписей.
RUN npm install -g pnpm@${PNPM_VERSION} \
&& npm cache clean --force
# Кэш npm чистим в том же слое, иначе он осядет в образе.
WORKDIR /app
############################################################
# Stage 1: prod-deps, только production-зависимости
############################################################
FROM base AS prod-deps
# Копируем ТОЛЬКО манифесты. Слой инвалидируется лишь при смене зависимостей,
# а не при каждом изменении исходников.
COPY package.json pnpm-lock.yaml ./
# --mount=type=cache: pnpm store живёт в кэше BuildKit, а не в слое образа,
# поэтому повторные сборки почти не ходят в сеть.
# --mount=type=secret: .npmrc с токеном приватного реестра (если он есть)
# доступен только на время RUN и НЕ попадает ни в один слой.
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
--mount=type=secret,id=npmrc,target=/root/.npmrc,required=false \
pnpm install --prod --frozen-lockfile --store-dir=/pnpm/store
############################################################
# Stage 2: build, полные зависимости + компиляция TypeScript
############################################################
FROM base AS build
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
--mount=type=secret,id=npmrc,target=/root/.npmrc,required=false \
pnpm install --frozen-lockfile --store-dir=/pnpm/store
# Исходники копируем ПОСЛЕ установки зависимостей: правка кода
# не заставит заново ставить node_modules.
COPY . .
RUN pnpm run build
# prod-deps и build независимы, и BuildKit выполняет их параллельно.
############################################################
# Stage 3: runtime, минимальный финальный образ
############################################################
FROM node:${NODE_VERSION}-${DEBIAN_RELEASE}-slim AS runtime
# tini в роли PID 1: корректно пробрасывает SIGTERM в node и собирает зомби-процессы.
# Без него node как PID 1 игнорирует SIGTERM, если приложение его не обрабатывает,
# и Kubernetes убивает под через SIGKILL после terminationGracePeriodSeconds.
# Заодно удаляем npm, npx, corepack и yarn: в рантайме они не нужны
# и только расширяют поверхность атаки (плюс их CVE).
RUN apt-get update \
&& apt-get install -y --no-install-recommends tini \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/local/lib/node_modules/npm \
/usr/local/lib/node_modules/corepack \
/usr/local/bin/npm /usr/local/bin/npx \
/usr/local/bin/corepack \
/usr/local/bin/yarn /usr/local/bin/yarnpkg \
/opt/yarn-*
ENV NODE_ENV=production \
PORT=4000
# NODE_ENV=production включает оптимизации Express (кэш шаблонов, короткие ошибки).
# Секреты (DB_URL, токены) сюда НЕ кладём: они приходят из K8s Secret в рантайме.
WORKDIR /app
# Файлы принадлежат root и доступны пользователю только на чтение:
# даже при RCE процесс не сможет подменить код приложения.
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
# package.json нужен, если в нём есть "type": "module" или его читает приложение.
# Непривилегированный пользователь node (UID 1000) уже есть в официальном образе.
# Указываем UID числом: иначе K8s с runAsNonRoot: true не сможет проверить,
# что пользователь не root, и откажется запускать под.
USER 1000:1000
EXPOSE 4000
# EXPOSE только документирует порт, реально он публикуется через Service или -p.
# Для docker run и compose. Kubernetes эту инструкцию ИГНОРИРУЕТ, там нужны probes.
# Используем встроенный в Node 20 fetch, curl ставить не нужно.
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD ["node", "-e", "fetch('http://127.0.0.1:4000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
# OCI-метаданные. ARG объявлены в конце, чтобы смена версии или SHA
# не инвалидировала кэш предыдущих слоёв.
ARG VERSION=dev
ARG GIT_SHA=unknown
LABEL org.opencontainers.image.title="shiplog-api" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${GIT_SHA}"
# exec-форма: без /bin/sh -c, сигналы доходят до процесса напрямую.
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "dist/server.js"]
Почему node:20-bookworm-slim, а не alpine
| Вариант | Размер (сжатый, ≈) | Плюсы | Минусы |
|---|---|---|---|
node:20-alpine |
~45 МБ | Самый маленький | musl вместо glibc: prebuilt-бинарники (bcrypt, sharp, argon2, Prisma) часто не работают или требуют пересборки; бывают тонкие различия в DNS-резолве |
node:20-bookworm-slim ✅ |
~70 МБ | glibc, максимальная совместимость, предсказуемое поведение | На ~25 МБ больше |
gcr.io/distroless/nodejs20-debian12:nonroot |
~45 МБ | Нет shell и пакетного менеджера, минимальная поверхность атаки | Нельзя сделать kubectl exec и отладить, нет tini (нужно обрабатывать SIGTERM в коде) |
Для продакшена в K8s разница в 25 МБ несущественна, а совместимость важнее. Если нативных зависимостей нет, можно смело взять alpine. Если нужна максимальная защищённость, берите distroless для финального stage.
Рекомендация: в CI фиксируйте образ по digest (node:20-bookworm-slim@sha256:...), а обновление автоматизируйте через Renovate или Dependabot.
2. .dockerignore
# Артефакты: собираются внутри образа
node_modules
dist
build
coverage
*.tsbuildinfo
# VCS и CI
.git
.gitignore
.github
.gitlab-ci.yml
# Секреты и локальные конфиги: НИКОГДА не в build-контекст
.env
.env.*
!.env.example
.npmrc
*.pem
*.key
# Docker-файлы (не нужны внутри образа, их изменения не должны бить кэш COPY . .)
Dockerfile*
docker-compose*.yml
.dockerignore
# IDE, ОС, логи
.vscode
.idea
*.swp
.DS_Store
*.log
npm-debug.log*
pnpm-debug.log*
# Документация и тесты (если тесты не запускаются в Dockerfile)
*.md
docs
test
tests
**/*.test.ts
**/*.spec.ts
__tests__
Если в
.npmrcесть несекретные настройки (например,node-linker,shamefully-hoist), вынесите их в отдельный закоммиченный файл и уберите.npmrcиз игнора. Токены передавайте только через--secret.
3. Сборка и запуск
VERSION=1.0.0
GIT_SHA=$(git rev-parse --short HEAD)
REGISTRY=cr.yandex/<registry-id>
# Сборка под AMD64 (важно, если собираете на Apple Silicon)
docker buildx build \
--platform linux/amd64 \
--build-arg VERSION=$VERSION \
--build-arg GIT_SHA=$GIT_SHA \
--secret id=npmrc,src=$HOME/.npmrc \
-t $REGISTRY/shiplog-api:$VERSION \
-t $REGISTRY/shiplog-api:$VERSION-$GIT_SHA \
--push .
# --secret нужен только при приватном npm-реестре, иначе строку можно убрать.
# Локальная проверка
docker buildx build --platform linux/amd64 --load -t shiplog-api:$VERSION .
docker run --rm -p 4000:4000 --env-file .env.local --read-only --tmpfs /tmp \
--cap-drop=ALL --security-opt no-new-privileges shiplog-api:$VERSION
curl -f http://localhost:4000/healthz
# Анализ
docker image ls shiplog-api:$VERSION # размер
docker history shiplog-api:$VERSION # слои
trivy image --severity HIGH,CRITICAL shiplog-api:$VERSION # уязвимости
Фрагмент манифеста Kubernetes, дополняющий образ:
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
readinessProbe: { httpGet: { path: /healthz, port: 4000 }, periodSeconds: 10 }
livenessProbe: { httpGet: { path: /healthz, port: 4000 }, initialDelaySeconds: 15, periodSeconds: 20 }
volumeMounts: [{ name: tmp, mountPath: /tmp }] # + volumes: [{ name: tmp, emptyDir: {} }]
4. Таблица решений
| Инструкция | Зачем | Размер | Скорость | Безопасность |
|---|---|---|---|---|
# syntax=docker/dockerfile:1.7 |
Включает cache и secret mounts | — | ⬆ | ⬆ |
ARG NODE_VERSION |
Обновление Node одной строкой | — | — | ⬆ (быстрый апгрейд) |
bookworm-slim |
Баланс размера и совместимости | ⬇ (vs full ~400 МБ) | — | ⬆ меньше пакетов |
| Multi-stage (4 stage) | Компилятор, devDeps и исходники TS не попадают в рантайм | ⬇⬇ | ⬆ параллельная сборка | ⬆ |
COPY package.json pnpm-lock.yaml до COPY . . |
Кэш слоя зависимостей | — | ⬆⬆ | — |
--mount=type=cache (pnpm store) |
Store вне слоёв, переиспользуется между сборками | ⬇ | ⬆⬆ | — |
--frozen-lockfile |
Воспроизводимость, защита от незаметной подмены версий | — | — | ⬆ supply chain |
--mount=type=secret |
Токен реестра не попадает в слои | — | — | ⬆⬆ |
pnpm install --prod в отдельном stage |
В рантайме нет devDependencies | ⬇⬇ | — | ⬆ |
apt-get … && rm -rf /var/lib/apt/lists/* в одном RUN |
Кэш apt не оседает в слое | ⬇ | — | — |
| Удаление npm/yarn/corepack | Меньше бинарников и CVE | ⬇ ~15 МБ | — | ⬆ |
tini |
Корректный SIGTERM и graceful shutdown | ⬆ ~1 МБ | ⬆ быстрый rollout | ⬆ стабильность |
Файлы root-owned + USER 1000:1000 |
Процесс не может менять свой код | — | — | ⬆⬆ |
ENV NODE_ENV=production |
Оптимизации Express и библиотек | — | ⬆ рантайм | ⬆ меньше утечек в ошибках |
HEALTHCHECK через node -e fetch |
Проверка здоровья без curl | — | — | — |
LABEL + ARG в конце |
Трассируемость образа до коммита без поломки кэша | — | ⬆ | ⬆ аудит |
exec-форма ENTRYPOINT/CMD |
Нет промежуточного shell | — | — | ⬆ |
5. Чек-лист дополнительных оптимизаций
- Перейти на Node 22 или 24 (Node 20 EOL).
- Зафиксировать базовый образ по
@sha256:digest и автоматизировать обновления (Renovate). - Graceful shutdown в коде: на
SIGTERMвызыватьserver.close(), закрывать пулы БД, выставлятьterminationGracePeriodSeconds. - Реализовать отдельные
/livezи/readyz(readiness проверяет БД, liveness нет). - Задавать лимит памяти через
NODE_OPTIONS=--max-old-space-size=<~75% limit>в Deployment, а не в образе. - Попробовать distroless-рантайм, если не нужен
kubectl exec. - Бандлинг через
esbuild/tsupв один файл:node_modulesв рантайме может сократиться в разы (осторожно с нативными модулями). -
pnpm deploy --prodили--filter, если проект станет монорепо. - Remote cache в CI:
--cache-to/--cache-from type=registry,ref=cr.yandex/.../shiplog-api:buildcache,mode=max. - Сканирование в CI (Trivy/Grype) с блокировкой на CRITICAL; Yandex Container Registry тоже умеет сканировать.
- SBOM и подпись образа:
--sbom=true --provenance=true, cosign. -
hadolint Dockerfileв pre-commit и CI. - Сборка source maps плюс
NODE_OPTIONS=--enable-source-mapsдля читаемых стектрейсов (небольшой оверхед).
Потенциальные проблемы и как их избежать
| Проблема | Причина | Решение |
|---|---|---|
| Раздутый образ | devDependencies, исходники TS, кэш pnpm/apt в слоях, full-образ Node | Multi-stage, --prod, cache mounts, очистка в том же RUN, slim-база |
| Утечка секретов | COPY .npmrc/.env, ARG NPM_TOKEN (виден в docker history), ENV SECRET=… |
.dockerignore, --mount=type=secret, секреты только из K8s Secret/Lockbox в рантайме |
| Медленный rebuild | COPY . . до pnpm install, отсутствие .dockerignore (огромный контекст с node_modules и .git), нет кэша в CI |
Порядок слоёв, cache mount, remote cache через registry |
| Нативные модули падают | alpine/musl или сборка на arm64 для amd64-кластера | glibc-образ, --platform linux/amd64 |
| Долгий rollout, 502 при деплое | node как PID 1 не получает SIGTERM | tini + обработчик SIGTERM + readinessProbe |
Под не стартует с runAsNonRoot |
USER node (не числовой) |
USER 1000:1000 |
Ошибка записи при readOnlyRootFilesystem |
Приложение или библиотеки пишут в /tmp или рядом с кодом |
emptyDir на /tmp, логи только в stdout |
Уточняющие вопросы
- Есть ли health-эндпоинт и по какому пути? Если нет, стоит добавить
/healthz(или убратьHEALTHCHECK). - Есть ли нативные зависимости (
bcrypt,sharp,prisma,argon2,canvas)? От этого зависит выбор alpine или slim. Для Prisma нужно ещё копировать сгенерированный клиент и, возможно, поставитьopenssl. - Используется ли приватный npm-реестр (
.npmrcс токеном)? - Нужны ли в рантайме файлы вне
dist/: миграции, SQL, шаблоны,public/, конфиги? - Используются ли
paths-алиасы вtsconfig?tscих не переписывает, понадобитсяtsc-aliasили бандлер. - Монорепо (pnpm workspaces) или одиночный пакет? Для монорепо схема меняется на
pnpm deploy. - Читает ли приложение порт из
PORTили он захардкожен? - Можно ли обновить Node до 22 или 24?
Ответьте на то, что расходится с допущениями, и я скорректирую Dockerfile под ваш проект.
Похожие промпты
Ревью и рефакторинг PowerShell-скрипта
Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.
Скрипт PowerShell под задачу
Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.
Диагностика проблем VPN-подключения
Методично диагностирует проблемы VPN: соединение не устанавливается, скорость падает, утекает DNS — по одному шагу, с командами под вашу ОС.
Помощник по настройке WireGuard и OpenVPN
Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.