Dockerfile
Зачем нужно
Dockerfile — текстовый файл с инструкциями для создания Docker-образа. Вместо ручной настройки окружения ты описываешь шаги в файле, и Docker автоматически собирает образ. Это как рецепт: любой может воспроизвести результат.
Где используется
- Сборка приложений — упаковка приложения в образ
- CI/CD — автоматическая сборка при каждом коммите
- Стандартизация — одинаковое окружение для всех
- Деплой — образ = готовое к запуску приложение
Основные инструкции
FROM — базовый образ
# Всегда первая инструкция
FROM node:20-alpine
# Указание конкретной версии — хорошая практика
FROM node:20.11.1-alpine3.19
# Минимальный образ
FROM alpine:3.19
WORKDIR — рабочая директория
# Устанавливает рабочую директорию внутри контейнера
WORKDIR /app
# Все последующие команды выполняются в /app
COPY и ADD — копирование файлов
# Копировать файлы из хоста в контейнер
COPY package.json .
COPY package-lock.json() .
COPY src/ ./src/
# COPY — предпочтительный вариант
# ADD — умеет распаковывать архивы и скачивать URL (редко нужно)
COPY . .
RUN — выполнение команд при сборке
# Установка зависимостей
RUN npm ci
# Несколько команд через &&
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
# Каждый RUN создаёт новый слой — объединяй команды
EXPOSE — документирование порта
# Указывает, какой порт слушает приложение
# Это документация, реальный проброс через docker run -p
EXPOSE 3000
ENV — переменные окружения
ENV NODE_ENV=production
ENV PORT=3000
CMD — команда запуска
# exec-форма (предпочтительная)
CMD ["node", "server.js"]
# shell-форма
CMD node server.js
# CMD можно переопределить при docker run
ENTRYPOINT — точка входа
# Определяет исполняемый файл
ENTRYPOINT ["node"]
# CMD становится аргументами для ENTRYPOINT
CMD ["server.js"]
# docker run my-app app.js → node app.js
Разница CMD vs ENTRYPOINT:
| CMD | ENTRYPOINT | |
|---|---|---|
Переопределяется при docker run |
Да, полностью | Только с --entrypoint |
| Назначение | Команда по умолчанию | Фиксированная точка входа |
| Использование | Самостоятельная команда | + CMD как аргументы |
ARG — аргументы сборки
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
ARG BUILD_DATE
LABEL build-date=$BUILD_DATE
# ARG доступен только при сборке, не в runtime
# docker build --build-arg NODE_VERSION=18 .
Пример: Node.js приложение
# === Базовый образ ===
FROM node:20-alpine
# === Рабочая директория ===
WORKDIR /app
# === Установка зависимостей (кэширование слоёв!) ===
COPY package.json package-lock.json() ./
RUN npm ci --only=production
# === Копирование исходников ===
COPY . .
# === Метаданные ===
EXPOSE 3000
ENV NODE_ENV=production
# === Запуск ===
CMD ["node", "src/index.js"]
Сборка и запуск:
docker build -t my-node-app .
docker run -d -p 3000:3000 my-node-app
Multi-stage builds
Multi-stage сборка позволяет использовать несколько FROM и копировать результаты между стадиями. Итоговый образ содержит только нужное.
# === Стадия 1: Сборка ===
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json() ./
RUN npm ci
COPY . .
RUN npm run build
# === Стадия 2: Продакшен ===
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json() ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
Результат: финальный образ не содержит devDependencies, исходники и инструменты сборки.
Multi-stage для фронтенда
# === Стадия 1: Сборка React/Vue/Angular ===
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json() ./
RUN npm ci
COPY . .
RUN npm run build
# === Стадия 2: Nginx для раздачи статики ===
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
.dockerignore
Файл .dockerignore исключает файлы из контекста сборки (аналог .gitignore).
# Зависимости
node_modules
npm-debug.log
# Git
.git
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# IDE
.vscode
.idea
# OS
.DS_Store
Thumbs.db
# Тесты и документация
__tests__
coverage
*.md
Зачем: ускоряет сборку и уменьшает размер контекста.
Лучшие практики
1. Порядок инструкций для кэширования
# ПРАВИЛЬНО — зависимости кэшируются отдельно
COPY package*.json() ./
RUN npm ci
COPY . .
# НЕПРАВИЛЬНО — при любом изменении кода зависимости
# переустанавливаются заново
COPY . .
RUN npm ci
2. Минимальный базовый образ
# Предпочитай alpine-варианты
FROM node:20-alpine # ~50MB
# вместо
FROM node:20 # ~350MB
3. Один процесс на контейнер
# ПРАВИЛЬНО — один контейнер = один сервис
CMD ["node", "server.js"]
# НЕПРАВИЛЬНО — не запускай несколько сервисов
CMD node server.js & nginx -g "daemon off;"
4. Непривилегированный пользователь
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Частые ошибки
| Ошибка | Проблема | Решение |
|---|---|---|
COPY . . без .dockerignore |
node_modules попадает в образ | Создай .dockerignore |
RUN npm install |
Нестабильные зависимости | Используй npm ci |
FROM node:latest |
Непредсказуемая версия | Указывай конкретную версию |
| Каждая команда в отдельном RUN | Много слоёв, большой образ | Объединяй через && |
| Секреты в ENV | Видны через docker inspect |
Используй secrets или build args |
Практика
- Напиши Dockerfile для простого Express-сервера
- Собери образ и проверь его размер через
docker images - Создай multi-stage Dockerfile для React-приложения
- Добавь
.dockerignoreи сравни время сборки - Запусти контейнер от непривилегированного пользователя
Связанные темы
- Что такое Docker — основы Docker
- Docker Compose — оркестрация контейнеров
- GitHub Actions — автоматическая сборка образов
- Автоматический деплой — деплой Docker-образов