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

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

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

Начать курс

Введение: Почему продвинутая контейнеризация TypeScript — это необходимость

Контейнеризация TypeScript-приложений с помощью Docker давно перестала быть просто трендом — это стандарт современной разработки. Однако базовые Dockerfile'ы, которые просто копируют весь проект и запускают npm start, далеки от идеала. На продвинутом уровне мы сталкиваемся с задачами оптимизации размера образов, обеспечения безопасности, организации микросервисной архитектуры и настройки автоматического восстановления.

В этой статье мы разберём техники, которые превратят ваш Docker-пайплайн для TypeScript в промышленный стандарт: многоэтапные сборки (multi-stage builds), минимизация слоёв, использование .dockerignore, Docker Compose для микросервисов, healthchecks и работа с переменными окружения. Все примеры написаны на TypeScript и готовы к интеграции в реальные проекты.

1. Многоэтапная сборка: от разработки до production

Главная проблема базовых Dockerfile — они включают в финальный образ всё: исходники, node_modules для разработки, инструменты сборки. Для TypeScript это критично, так как tsc и devDependencies не нужны в production.

Многоэтапная сборка (multi-stage build) решает эту задачу: первый этап собирает и компилирует TypeScript, второй — создаёт минимальный образ только с необходимыми файлами.

Пример Dockerfile с двумя этапами

# Этап 1: Сборка TypeScript
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# Этап 2: Production-образ
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

Ключевые моменты:

  • Используем npm ci — точное воспроизведение зависимостей из lock-файла.
  • Копируем только dist/ и node_modules — исходники TypeScript не попадают в production.
  • Alpine-образ — минимальная основа (около 5 MB).

Такой образ весит в 3-5 раз меньше, чем наивная сборка. Для большого проекта с десятками зависимостей разница может достигать сотен мегабайт.

Трёхэтапная сборка с dev-зависимостями

Иногда нужно разделить dev- и prod-зависимости. Добавляем третий этап для установки только production-зависимостей:

# Этап 1: Установка всех зависимостей
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Этап 2: Сборка
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Этап 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

2. Оптимизация образа: .dockerignore, кэширование и слои

Каждая инструкция COPY, RUN и ADD создаёт новый слой в Docker. Чем меньше слоёв и чем эффективнее они кэшируются, тем быстрее происходит сборка.

.dockerignore — ваш первый помощник

Создайте файл .dockerignore в корне проекта:

node_modules
dist
.git
.gitignore
*.md
.env
.env.*
Dockerfile
.dockerignore
coverage
.vscode
.idea
__tests__

Это предотвращает копирование мусора в контекст сборки и ускоряет передачу файлов демону Docker.

Правильный порядок инструкций

Docker кэширует слои. Если вы сначала копируете package.json, а потом запускаете npm ci, этот слой будет переиспользован, пока не изменится package.json. Затем копируйте исходники — это последний слой, который меняется чаще всего.

# Правильный порядок (кэширование зависимостей)
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

Если вы скопируете все файлы до установки зависимостей, кэш будет сбрасываться при любом изменении любого файла.

Минимизация количества слоёв

Объединяйте связанные команды в один RUN:

# Плохо (3 слоя)
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Хорошо (1 слой)
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

3. Docker Compose для TypeScript микросервисов

Когда ваше приложение состоит из нескольких сервисов (API, база данных, кэш, очередь), Docker Compose становится незаменимым. Рассмотрим пример микросервисной архитектуры на TypeScript.

Пример docker-compose.yml

version: '3.8'
services:
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - REDIS_HOST=redis
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 5s
      retries: 3

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

vol                

Блоги

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