Почему контейнеризация TypeScript приложений — это маст-хев для современного разработчика
Представьте ситуацию: вы написали крутое TypeScript-приложение на своём MacBook, всё работает идеально. Вы заливаете код на сервер под Ubuntu — и получаете кучу ошибок. Знакомо? Проблема в различии окружений: версии Node.js, системные библиотеки, переменные среды. Docker решает эту боль раз и навсегда. Контейнеризация TypeScript приложений позволяет упаковать код вместе со всеми зависимостями в изолированный контейнер, который запустится одинаково на любом сервере, в облаке или на локальной машине коллеги.
В этой статье мы разберём, как правильно контейнеризировать TypeScript-приложение с помощью Docker. Вы узнаете не только базовые шаги, но и продвинутые техники: мультистейдж-билды, оптимизацию размера образа, кэширование зависимостей и работу с переменными окружения. Погнали!
Что такое Docker и зачем он TypeScript-разработчику
Docker — это платформа для создания, доставки и запуска приложений в контейнерах. Контейнер — это лёгкая, автономная среда, которая содержит всё необходимое для работы вашего TypeScript-приложения: код, рантайм (Node.js), системные инструменты, библиотеки и настройки.
Преимущества для TypeScript-проектов очевидны:
- Воспроизводимость окружения — больше никаких «у меня работает» и «упало на продакшене».
- Изоляция — каждое приложение работает в своём контейнере, не мешая другим сервисам.
- Масштабирование — легко запустить 10 экземпляров вашего TypeScript-сервера через Docker Compose или Kubernetes.
- Простота деплоя — один образ можно запустить на любой платформе: AWS, Google Cloud, DigitalOcean или локально.
Предварительные требования
Перед тем как начать, убедитесь, что у вас установлены:
- Docker — скачайте с официального сайта (работает на Windows, macOS, Linux).
- Node.js версии 18 или выше (для компиляции TypeScript).
- TypeScript — установлен глобально или локально в проекте.
В качестве примера мы возьмём простое Express-приложение на TypeScript. Но подход универсален: вы можете контейнеризировать любой TypeScript-проект — от Telegram-бота до NestJS-сервера.
Структура проекта
Для начала создадим минимальную структуру TypeScript-приложения:
my-typescript-app/
├── src/
│ └── index.ts
├── package.json
├── tsconfig.json
└── Dockerfile
Файл src/index.ts будет содержать простой HTTP-сервер:
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 is running on port ${PORT}`);
});
Файл tsconfig.json с базовыми настройками:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
Пишем первый Dockerfile для TypeScript приложения
Самый простой способ — сделать одноэтапный билд. Но мы сразу пойдём по правильному пути и создадим мультистейдж-билд. Это позволит уменьшить финальный образ в несколько раз.
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm install --production
EXPOSE 3000
CMD ["node", "dist/index.js"]
Разберём каждый этап:
- builder — устанавливаем все зависимости (включая devDependencies), копируем исходники и компилируем TypeScript в JavaScript.
- production — берём только скомпилированные файлы из первого этапа и устанавливаем только продакшен-зависимости. Это резко уменьшает размер образа.
Сборка и запуск контейнера
Собираем образ командой:
docker build -t my-typescript-app .
Запускаем контейнер:
docker run -p 3000:3000 -d my-typescript-app
Флаг -p 3000:3000 пробрасывает порт контейнера на хост. Теперь открываем браузер и видим Hello from TypeScript + Docker!. Вуаля! Вы только что успешно выполнили контейнеризацию TypeScript приложения.
Оптимизация Docker образа: кэширование зависимостей
Одна из частых ошибок новичков — копировать все файлы до установки зависимостей. Каждое изменение в исходниках будет сбрасывать кэш Docker и переустанавливать npm пакеты. Правильный подход — копировать package.json отдельно:
COPY package*.json ./
RUN npm install
COPY . .
Так Docker будет переустанавливать зависимости только когда меняется package.json. Это экономит минуты при каждой сборке.
Использование .dockerignore
Создайте файл .dockerignore в корне проекта, чтобы не копировать лишние файлы в образ:
node_modules
dist
.git
*.md
.env
Это ускоряет сборку и уменьшает размер образа.
Работа с переменными окружения в Docker и TypeScript
В реальных проектах вы будете использовать переменные окружения: API-ключи, строки подключения к БД, порты. В TypeScript-приложении мы уже используем process.env.PORT. При запуске контейнера передаём их через флаг -e:
docker run -p 3000:3000 -e PORT=4000 -d my-typescript-app
Для продакшена удобнее использовать файл .env и запускать через docker run --env-file .env .... Или, если вы используете Docker Compose, пропишите переменные в docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- PORT=3000
- NODE_ENV=production
Docker Compose для TypeScript приложений
Если ваше приложение состоит из нескольких сервисов (например, TypeScript-бекенд + PostgreSQL + Redis), Docker Compose — иде