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

Введение
Docker стал стандартом упаковки приложений в индустрии. Он решает классическую проблему «у меня на машине работает»: код вместе со всем окружением запаковывается в контейнер, который одинаково запускается на ноутбуке, сервере и в облаке. В этом гайде мы разберём базовые концепции, научимся писать Dockerfile, поднимать многосервисные приложения через docker-compose и подготавливать образы к продакшену.
Что такое контейнер и образ
Контейнер — это изолированный процесс на хосте, использующий ядро Linux, но со своей файловой системой, сетью и ресурсами. Образ (image) — это шаблон, из которого создаются контейнеры. Образ состоит из слоёв: каждый слой — это изменения файловой системы относительно предыдущего.
Главное отличие от виртуальной машины: контейнер не запускает собственное ядро, поэтому стартует за миллисекунды и потребляет минимум ресурсов.
Первый запуск
После установки Docker Desktop или Docker Engine проверим, что всё работает:
# Проверяем версию клиента и демона
docker version
# Запускаем тестовый контейнер
docker run hello-world
Команда docker run скачивает образ из Docker Hub, если его нет локально, создаёт из него контейнер и запускает.
Попробуем что-то полезнее — поднимем nginx:
# -d запускает в фоне, -p пробрасывает порт хоста в контейнер
docker run -d -p 8080:80 --name web nginx:alpine
# Смотрим запущенные контейнеры
docker ps
# Останавливаем и удаляем
docker stop web && docker rm web
Открыв http://localhost:8080, увидим страницу приветствия nginx.
Dockerfile — рецепт образа
Dockerfile описывает, как собрать собственный образ. Возьмём для примера Node.js приложение:
# Базовый образ с Node.js 20 на Alpine Linux
FROM node:20-alpine
# Рабочая директория внутри контейнера
WORKDIR /app
# Копируем манифесты отдельно — для кэша слоёв
COPY package*.json ./
# Устанавливаем только production-зависимости
RUN npm ci --omit=dev
# Копируем остальной код
COPY . .
# Указываем порт, на котором слушает приложение
EXPOSE 3000
# Команда запуска по умолчанию
CMD ["node", "server.js"]
Собираем образ и запускаем контейнер:
# -t задаёт имя и тег, точка — путь к контексту сборки
docker build -t my-app:1.0 .
# Запускаем приложение на 3000-м порту
docker run -d -p 3000:3000 --name api my-app:1.0
Порядок инструкций важен
Docker кэширует каждый слой. Если изменился исходный код, но package.json не трогали, переустановка зависимостей не запустится — слой возьмётся из кэша. Поэтому копировать манифесты нужно ДО копирования исходников.
Многостадийная сборка
Для компилируемых языков и фронтенда удобно использовать multi-stage build: собираем в одном образе, а в финальный кладём только артефакт.
# Стадия сборки
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Собираем продакшен-бандл
RUN npm run build
# Финальная стадия — минимальный образ
FROM nginx:alpine
# Копируем только готовую сборку из builder
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
Итоговый образ получится в разы меньше: ни node_modules, ни исходников, только статика и nginx.
Volumes — сохранение данных
Контейнер по умолчанию эфемерен: удалили — потеряли данные. Для постоянного хранения используются volumes.
# Именованный volume для данных PostgreSQL
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpine
Для разработки удобно пробрасывать локальную папку (bind mount), чтобы изменения кода сразу попадали в контейнер:
# Пробрасываем текущую папку в /app
docker run -it -v $(pwd):/app -w /app node:20-alpine npm run dev
docker-compose — несколько сервисов
Реальное приложение редко состоит из одного контейнера. docker-compose описывает всю инфраструктуру одним YAML-файлом.
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Запуск и остановка:
# Поднимаем стек в фоне
docker compose up -d
# Смотрим логи конкретного сервиса
docker compose logs -f api
# Останавливаем и удаляем контейнеры
docker compose down
Внутри compose-сети сервисы видят друг друга по имени: api ходит в БД по адресу db:5432, никакого localhost.
Подготовка к продакшену
Несколько правил, которые стоит соблюдать:
- Используйте конкретные теги образов (
node:20.11-alpine), а неlatest— это даёт воспроизводимость сборки. - Не запускайте процесс от root: добавьте инструкцию
USER nodeпосле установки зависимостей. - Складывайте секреты в переменные окружения или secret-механизмы оркестратора, никогда не зашивайте в образ.
- Минимизируйте размер: alpine-базы, multi-stage,
.dockerignoreдля исключенияnode_modules,.git, тестов.
Пример .dockerignore:
node_modules
npm-debug.log
.git
.env
dist
coverage
Частые ошибки
Копирование всего подряд до установки зависимостей. Любое изменение файла ломает кэш слоя с npm install, и сборка занимает минуты вместо секунд.
Использование тега latest в продакшене. Через месяц образ обновится, и сборка может сломаться без видимых изменений в коде.
Хранение данных в контейнере без volume. После docker rm база данных исчезнет вместе с контейнером.
Запуск от root. Если злоумышленник получит контроль над процессом внутри контейнера, у него будут root-права на файлы volume.
Игнорирование размера образа. Образ на 1.5 ГБ — это медленный pull в CI, медленный деплой и лишние деньги за трафик. Alpine-базы и multi-stage решают проблему.
Логи в файл вместо stdout. Docker собирает логи из stdout/stderr. Если приложение пишет в файл внутри контейнера, его не увидят ни docker logs, ни системы агрегации.
Заключение
Docker даёт разработчику воспроизводимое окружение, быстрый онбординг новых членов команды и единый формат поставки приложения. Освоив базовые команды, Dockerfile и docker-compose, вы сможете локально поднимать любую инфраструктуру — от простой связки API и базы до многосервисных систем с очередями и кэшами. Следующий шаг — изучить оркестраторы вроде Kubernetes и CI/CD-пайплайны, в которых Docker-образы являются основной единицей деплоя.






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