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

Практика

  1. Напиши Dockerfile для простого Express-сервера
  2. Собери образ и проверь его размер через docker images
  3. Создай multi-stage Dockerfile для React-приложения
  4. Добавь .dockerignore и сравни время сборки
  5. Запусти контейнер от непривилегированного пользователя

Связанные темы

Ресурсы