Docker и TypeScript: контейнеризация приложений для разработчиков

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Docker и TypeScript: зачем нужна контейнеризация

Современная веб-разработка немыслима без инструментов, обеспечивающих единообразие окружения. Docker стал стандартом де-факто для упаковки, доставки и запуска приложений. В сочетании с TypeScript — строго типизированным языком, компилирующимся в JavaScript — контейнеризация даёт мощный инструмент для создания масштабируемых и надёжных сервисов.

Зачем контейнеризировать TypeScript-приложение? Во-первых, это решает проблему «на моей машине работает». Во-вторых, упрощает развёртывание в production и CI/CD. В-третьих, позволяет изолировать зависимости и версии Node.js. В этой статье мы разберём, как создать Docker-образ для TypeScript-приложения, настроить multi-stage сборку и использовать docker-compose для локальной разработки.

Основы Docker для TypeScript-разработчика

Прежде чем писать код, напомним базовые понятия Docker: образ (image) — шаблон для создания контейнера; контейнер (container) — запущенный экземпляр образа; Dockerfile — инструкция по сборке образа.

Для TypeScript-приложения на Node.js нам понадобятся:

  • Node.js runtime (обычно образ node:18-alpine или node:20-alpine для минимального размера)
  • Установка зависимостей (npm install или yarn install)
  • Компиляция TypeScript в JavaScript (tsc)
  • Запуск скомпилированного приложения

Рассмотрим простой пример TypeScript-приложения (файл src/index.ts):

import express from 'express';

const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from TypeScript + Docker!');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

И tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Теперь создадим Dockerfile для этого приложения.

Создание Dockerfile для TypeScript-приложения

Базовый Dockerfile может выглядеть так:

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]

Здесь используется multi-stage build (многоступенчатая сборка). Первая стадия (builder) собирает TypeScript в JavaScript, вторая (runner) содержит только скомпилированный код и runtime-зависимости. Это уменьшает размер финального образа — в него не попадают исходники TypeScript и dev-зависимости.

Важно: мы выполняем npm ci (чистая установка) вместо npm install для воспроизводимости и скорости.

Оптимизация Dockerfile: кэширование слоёв

Docker кэширует каждый слой (инструкцию). Чтобы ускорить сборку, копируйте файлы, которые меняются редко, раньше. Например, package.json и package-lock.json копируются до всех исходников, поэтому изменения в коде не приводят к переустановке зависимостей.

Дополнительно можно добавить .dockerignore:

node_modules
dist
.git
*.md
.gitignore
.env

Это предотвращает попадание лишних файлов в контекст сборки.

docker-compose для локальной разработки

Для удобства локальной разработки используйте docker-compose.yml. Он позволяет запускать приложение с автоматической перезагрузкой (используя nodemon или ts-node-dev) и подключать базы данных или другие сервисы.

Пример docker-compose.yml:

version: '3.8'
services:
  app:
    build:
      context: .
      target: builder
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    command: npx ts-node-dev --respawn src/index.ts
    environment:
      - NODE_ENV=development

Объяснение:

  • build.context — путь к Dockerfile
  • volumes — монтирование текущей директории в контейнер, что позволяет видеть изменения в реальном времени
  • command — используем ts-node-dev для горячей перезагрузки

Для production можно создать отдельный compose-файл, использующий стадию runner.

Лучшие практики контейнеризации TypeScript-приложений

1. Используйте минимальные базовые образы

Выбирайте alpine версии (node:18-alpine). Это уменьшает размер образа и поверхность атаки. Для production можно дополнительно использовать distroless образы от Google.

2. Разделяйте dev и production сборки

В development можно устанавливать dev-зависимости (TypeScript, тестовые библиотеки) и использовать ts-node. В production — только скомпилированный JS и runtime-зависимости.

3. Обрабатывайте сигналы остановки

Docker отправляет контейнеру сигнал SIGTERM. Убедитесь, что ваше приложение корректно завершает работу. В Express можно добавить:

process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down...');
  server.close(() => process.exit(0));
});

4. Используйте healthcheck

Добавьте в Dockerfile проверку здоровья контейнера:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/ || exit 1

5. Не запускайте контейнеры

Блоги

Рекомендаций по книгам