Антон Ларичев

Введение
Docker — это платформа для упаковки приложений в изолированные контейнеры. Контейнер включает код, зависимости и системные библиотеки, поэтому приложение работает одинаково на ноутбуке разработчика, в CI и на проде. Для разработчика это означает конец историй «у меня всё работает» и быстрый онбординг новых сотрудников.
В этой статье разберём три ключевых понятия: образы (images), контейнеры (containers) и тома (volumes). Покажем команды и Dockerfile на практических примерах.
Образы: шаблон для запуска
Образ — это неизменяемый снимок файловой системы и метаданных, из которого создаются контейнеры. Образ собирается из инструкций в Dockerfile слой за слоем. Каждый слой кэшируется, поэтому повторная сборка идёт быстрее.
Простой Dockerfile для Node.js приложения:
# Базовый образ с предустановленным Node.js
FROM node:20-alpine
# Рабочая директория внутри контейнера
WORKDIR /app
# Сначала копируем только манифесты — кэш слоя сохранится,
# пока package.json не изменится
COPY package*.json ./
RUN npm ci --omit=dev
# Теперь копируем исходники
COPY . .
# Порт, который слушает приложение
EXPOSE 3000
# Команда запуска при старте контейнера
CMD ["node", "server.js"]
Собираем образ и смотрим список локальных образов:
# Тег формата имя:версия
docker build -t my-app:1.0 .
# Просмотр локальных образов
docker images
Для минимизации размера используйте alpine-варианты базовых образов и multi-stage сборку, когда нужны разные окружения для сборки и рантайма.
Multi-stage сборка
# Этап сборки: устанавливаем dev-зависимости и компилируем TypeScript
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Финальный этап: только runtime, без исходников и dev-зависимостей
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Такой подход сокращает размер итогового образа в разы и убирает лишние инструменты из прод-окружения.
Контейнеры: запущенный экземпляр
Контейнер — это рабочий процесс, созданный из образа. У него своя файловая система, сеть и пространство процессов. Контейнеры эфемерны: удаление контейнера стирает все изменения в его файловой системе, если они не сохранены в томе.
Запуск контейнера в фоне с пробросом портов:
# -d — detached режим, контейнер работает в фоне
# -p — проброс портов host:container
# --name — человекочитаемое имя
docker run -d -p 3000:3000 --name api my-app:1.0
# Логи контейнера
docker logs -f api
# Войти внутрь работающего контейнера
docker exec -it api sh
# Остановить и удалить
docker stop api && docker rm api
Переменные окружения передаются через -e или файл --env-file:
docker run -d \
-e NODE_ENV=production \
-e DATABASE_URL=postgres://user:pass@db:5432/app \
--name api my-app:1.0
Тома: постоянные данные
Том (volume) — механизм хранения данных вне жизненного цикла контейнера. Тома нужны базам данных, загруженным файлам и любому состоянию, которое должно пережить перезапуск.
Есть три варианта монтирования:
- Named volume — управляется Docker, хранится в
/var/lib/docker/volumes. - Bind mount — конкретная директория с хоста, удобно для разработки.
- tmpfs — временное хранилище в оперативной памяти.
Пример с PostgreSQL и именованным томом:
# Создаём том явно
docker volume create pgdata
# Монтируем том в директорию данных Postgres
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16-alpine
Для локальной разработки удобен bind mount — изменения в коде сразу видны контейнеру:
# Текущая директория монтируется в /app
docker run -d \
-p 3000:3000 \
-v $(pwd):/app \
-v /app/node_modules \
--name dev-api my-app:1.0
Анонимный том /app/node_modules защищает зависимости контейнера от перезаписи пустой локальной папкой.
Частые ошибки
- Запись в файловую систему контейнера без тома. После
docker rmданные исчезают. Любое постоянное состояние — в volume. COPY . .до установки зависимостей. Любая правка кода сбрасывает кэшnpm ci, сборка становится медленной. Сначала копируйте манифесты.- Запуск под root. По умолчанию процессы в контейнере идут от root, что увеличивает риск при побеге из контейнера. Добавляйте
USER nodeили собственного пользователя. - Жирные образы. Использование
ubuntuвместоalpineилиdistrolessбез причины раздувает образ на сотни мегабайт. - Хардкод секретов в Dockerfile. Значения попадают в слои образа. Используйте переменные окружения, BuildKit secrets или внешние хранилища.
- Игнорирование
.dockerignore. Без него в образ попадаютnode_modules,.gitи локальные конфиги, что ломает сборку и раздувает контекст.
Заключение
Образы — это шаблоны, контейнеры — их запущенные экземпляры, а тома — способ сохранить данные между запусками. Этих трёх понятий достаточно, чтобы упаковать сервис, поднять локальное окружение с базой и подготовить артефакт для деплоя.
Следующие шаги: освойте docker compose для оркестрации нескольких сервисов, настройте multi-stage сборку для прод-образов и подключите сканер уязвимостей вроде Trivy в CI. Docker — это базовый навык, без которого современная backend-разработка едва ли возможна.






Комментарии
0