26. Docker для Node.js

Docker — контейнеризация приложения. Работает одинаково на любой машине: dev, staging, production.
Зачем Docker?
Заголовок раздела «Зачем Docker?»Без Docker:"У меня на машине работает!" → На сервере — нетРазные версии Node → конфликтыРазные ОС → несовместимостиРучная установка зависимостей
С Docker:Один Dockerfile → одинаковое окружение вездеNode, npm, зависимости — всё внутри контейнераВоспроизводимые сборкиМасштабирование — запустил ещё один контейнерDockerfile — базовый
Заголовок раздела «Dockerfile — базовый»# DockerfileFROM node:20-alpine
# Рабочая директорияWORKDIR /app
# Копируем package файлы (кэширование слоёв)COPY package.json package-lock.json ./
# Устанавливаем зависимостиRUN npm ci --only=production
# Копируем исходный кодCOPY src/ ./src/
# Переменные окруженияENV NODE_ENV=productionENV PORT=3000
# ПортEXPOSE 3000
# ЗапускCMD ["node", "src/index.js"]Dockerfile — production-ready
Заголовок раздела «Dockerfile — production-ready»# Многоэтапная сборка (multi-stage)
# Этап 1: Установка зависимостейFROM node:20-alpine AS depsWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci
# Этап 2: Сборка (если есть TypeScript)FROM node:20-alpine AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .RUN npm run build# После сборки — только production depsRUN npm ci --only=production && npm cache clean --force
# Этап 3: RuntimeFROM node:20-alpine AS runnerWORKDIR /app
# Безопасность: не root пользовательRUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nodeuser
# Копируем только нужноеCOPY --from=builder --chown=nodeuser:nodejs /app/node_modules ./node_modulesCOPY --from=builder --chown=nodeuser:nodejs /app/dist ./distCOPY --from=builder --chown=nodeuser:nodejs /app/package.json ./
# Переключаемся на безопасного пользователяUSER nodeuser
ENV NODE_ENV=productionENV PORT=3000EXPOSE 3000
# HealthcheckHEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"].dockerignore
Заголовок раздела «.dockerignore»# .dockerignore — не копировать в контейнерnode_modulesnpm-debug.log.git.gitignore.env.env.localDockerfiledocker-compose.ymlREADME.mdtests/coverage/.vscode/.idea/*.mdКоманды Docker
Заголовок раздела «Команды Docker»# Сборка образаdocker build -t my-api .docker build -t my-api:1.0.0 . # с тегом версии
# Запуск контейнераdocker run -p 3000:3000 my-apidocker run -d -p 3000:3000 my-api # в фоне (detached)docker run -d \ --name my-api \ -p 3000:3000 \ -e DATABASE_URL=postgres://... \ -e JWT_SECRET=secret \ --restart unless-stopped \ my-api
# Управление контейнерамиdocker ps # запущенныеdocker ps -a # все (включая остановленные)docker logs my-api # логиdocker logs -f my-api # логи в реальном времениdocker exec -it my-api sh # зайти внутрь контейнераdocker stop my-api # остановитьdocker rm my-api # удалитьdocker image ls # список образовdocker image rm my-api # удалить образdocker-compose — несколько контейнеров
Заголовок раздела «docker-compose — несколько контейнеров»version: '3.8'
services: # API сервер api: build: . ports: - "3000:3000" environment: - NODE_ENV=production - PORT=3000 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET} depends_on: db: condition: service_healthy redis: condition: service_started restart: unless-stopped
# PostgreSQL db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: myapp volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5
# Redis (кэш, сессии, очередь) redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data
volumes: postgres_data: redis_data:# docker-compose командыdocker compose up # запустить всёdocker compose up -d # в фонеdocker compose up --build # пересобрать и запуститьdocker compose down # остановить и удалитьdocker compose down -v # + удалить volumes (данные!)docker compose logs -f api # логи конкретного сервисаdocker compose exec api sh # зайти в контейнерdocker compose ps # статус сервисовDev vs Production compose
Заголовок раздела «Dev vs Production compose»# docker-compose.dev.yml — для разработкиversion: '3.8'
services: api: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" - "9229:9229" # для Node.js debugger volumes: - .:/app # монтируем код (hot reload!) - /app/node_modules # но НЕ node_modules environment: - NODE_ENV=development command: npx nodemon --inspect=0.0.0.0:9229 src/index.js# Запуск dev окруженияdocker compose -f docker-compose.yml -f docker-compose.dev.yml upHealth Check эндпоинт
Заголовок раздела «Health Check эндпоинт»// routes/health.js — для Docker и мониторингаapp.get('/health', async (req, res) => { try { // Проверяем БД await db.$queryRaw`SELECT 1`;
res.json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), }); } catch (err) { res.status(503).json({ status: 'error', message: 'Database connection failed', }); }});Практика
Заголовок раздела «Практика»- Создай Dockerfile для Express API с multi-stage build
- Напиши .dockerignore — исключи всё лишнее
- Создай docker-compose.yml с API + PostgreSQL + Redis
- Добавь healthcheck для API контейнера
- Запусти
docker compose upи убедись что всё работает