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

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

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

Начать курс

Введение: зачем контейнеризировать TypeScript-приложения?

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

В этой статье мы разберём, как правильно контейнеризировать TypeScript-приложение: от базового Dockerfile до production-ready сборки с использованием docker-compose и многостадийной сборки. Вы узнаете, как избежать типичных ошибок, уменьшить размер образа и ускорить развёртывание.

Основы: TypeScript и Node.js в Docker

TypeScript — это язык, который компилируется в JavaScript. Это означает, что для запуска TypeScript-приложения в контейнере нам нужно сначала скомпилировать исходный код, а затем выполнить полученный JavaScript с помощью Node.js. Docker идеально подходит для автоматизации этого процесса.

Создание простого TypeScript-приложения

Для начала создадим минимальное приложение на TypeScript. Структура проекта:

my-ts-app/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── Dockerfile

src/index.ts:

import express, { Request, Response } from 'express';

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

app.get('/', (req: Request, res: Response) => {
  res.json({ message: 'Hello from TypeScript + Docker!' });
});

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

package.json:

{
  "name": "my-ts-app",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.0",
    "typescript": "^5.3.3",
    "ts-node": "^10.9.2"
  }
}

tsconfig.json:

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

Базовый Dockerfile

Первый, самый простой вариант Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

Этот Dockerfile копирует весь проект, устанавливает зависимости, компилирует TypeScript и запускает приложение. Однако у этого подхода есть недостатки: размер образа большой, а кэширование слоёв неэффективно.

Оптимизация: многостадийная сборка и уменьшение размера

Для production-среды рекомендуется использовать многостадийную сборку (multi-stage build). Это позволяет разделить этапы сборки и выполнения, оставляя в финальном образе только необходимые файлы.

Production Dockerfile с многостадийной сборкой

# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000

USER node

CMD ["node", "dist/index.js"]

Преимущества такого подхода:

  • Меньший размер образа — финальный слой содержит только скомпилированный код и production-зависимости.
  • Безопасность — в контейнере не остаётся исходного кода TypeScript, dev-зависимостей и инструментов сборки.
  • Быстрое кэширование — изменения в исходном коде не влияют на слой с зависимостями.
  • Запуск от непривилегированного пользователя — директива USER node повышает безопасность.

Оптимизация с помощью .dockerignore

Файл .dockerignore предотвращает копирование ненужных файлов в контекст сборки:

node_modules
dist
.git
.gitignore
*.md
.env
.env.*
Dockerfile
docker-compose.yml

Docker Compose: оркестрация для разработки и продакшена

Docker Compose позволяет управлять несколькими контейнерами одновременно. Для TypeScript-приложения это особенно полезно при наличии базы данных, очередей или других сервисов.

docker-compose.yml для разработки

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - PORT=3000
    command: npm run dev

Для разработки используем отдельный Dockerfile (Dockerfile.dev):

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]

Монтирование тома (volumes) позволяет автоматически перезагружать приложение при изменении кода (с помощью ts-node-dev или nodemon).

docker-compose.yml для продакшена

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    restart: unless-stopped

Продвинутые техники и best practices

1. Использование Healthcheck

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

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1

2. Логирование

Блоги

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