Dockerfile для production: сборка, кэш и запуск в Inquir
Разбираем Dockerfile для Node.js и TypeScript: отдельные стадии, lock-файл, пользователь процесса, секреты и проверка контейнера перед деплоем.
Приложение запускается на ноутбуке, но облачная сборка не находит пакет. Или образ собирается за несколько минут после изменения одной строки. Часто причина находится в Dockerfile: он переносит в контейнер случайное состояние рабочего каталога вместо воспроизводимого приложения.
Ниже — пример для Node.js и TypeScript. Предполагается, что npm run build создаёт dist/server.js, приложение слушает 0.0.0.0:3000, а /healthz возвращает успешный HTTP-ответ. Это контракт примера: если ваш проект устроен иначе, замените пути и порт до запуска команд.
Разделите сборку и выполнение
Компилятор TypeScript нужен при сборке, но обычно не нужен процессу, исполняющему готовый JavaScript. Разделение стадий позволяет явно выбрать, какие файлы попадут в финальный образ:
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:22-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build --chown=node:node /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Если сборка использует шаблоны, Prisma, статические файлы или конфигурацию вне src, их тоже нужно явно скопировать. Проверьте lifecycle-скрипты npm: например, prepare, запускающий компилятор, не сможет работать в runtime-стадии без devDependencies. Решайте это в структуре проекта, а не отключайте все install-скрипты вслепую — они могут понадобиться нативным зависимостям.
Обе стадии используют одну базовую систему. Это важно для нативных модулей: сборка в одном окружении и исполнение в другом могут закончиться ошибкой загрузки библиотеки. Для строгого контроля зафиксируйте проверенный digest базового образа и заведите регулярное обновление. Сам по себе неизменный digest не устанавливает исправления безопасности.
Что именно ускоряет кэш
Файлы зависимостей копируются раньше исходников. Изменение обработчика не должно заставлять заново устанавливать все пакеты, если lock-файл остался прежним и кэш сборщика доступен. Удалённый сборщик может начать с пустого кэша, поэтому воспринимайте это как возможность ускорения, а не гарантию времени.
npm ci помогает обнаружить несогласованность package.json и lock-файла. Сохраните оба в репозитории. Не копируйте локальный node_modules: он может содержать пакеты для другой ОС и архитектуры. Эти принципы также описаны в рекомендациях Docker.
Ограничьте контекст сборки
Создайте .dockerignore рядом с Dockerfile:
.git
node_modules
dist
coverage
.env
.env.*
*.log
Здесь dist исключён намеренно: он собирается внутри контейнера. Если вы упаковываете заранее подготовленный артефакт, правило будет другим. Проверьте также сертификаты, выгрузки базы и локальные каталоги с пользовательскими файлами: приведённый список не может знать все имена в вашем проекте.
Пароль реестра или базы не должен попадать в ARG, ENV или COPY. У Docker есть механизм build secrets, но способ передачи такого секрета зависит от конкретного сборочного процесса. Этот пример не предполагает, что Inquir автоматически передаёт локальные Docker build secrets. Для приватных пакетов сначала настройте поддерживаемый способ доступа в вашей сборочной среде.
Проверьте готовый образ локально
docker build -t inquir-api-check .
docker run --rm -p 127.0.0.1:3000:3000 inquir-api-check
В другом терминале запросите http://127.0.0.1:3000/healthz и один настоящий маршрут API. Публикация порта только на localhost здесь намеренная. Убедитесь, что запуск не требует TypeScript-компилятора, файлов с ноутбука или записи в /app. Пользователь node не получает права на произвольные каталоги автоматически.
Разместите приложение в Inquir Compute
После установки и авторизации CLI выполните из каталога проекта:
inquir apps create ts-api --ingress public --port 3000 --health-path /healthz
inquir apps deploy ts-api --promote
inquir apps status ts-api
inquir apps logs ts-api --no-follow --tail 100
create нужен один раз. Адрес получите из status; имя ts-api в примере не задаёт заранее известный домен. Секреты рабочего приложения задайте через его настройки переменных, а не через Dockerfile.
Проверьте на выданном адресе не только health check, но и ответ вашего API. Успешная сборка подтверждает создание образа, но не доступность базы, правильность ключа внешнего сервиса или работоспособность бизнес-сценария. Если контейнер собрался, а сайт не отвечает, используйте порядок диагностики деплоя.