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

Введение
Docker — это платформа для упаковки приложений вместе с их зависимостями в изолированные контейнеры. Контейнер можно запустить на любой машине, где установлен Docker, и он будет работать одинаково: на ноутбуке разработчика, на сервере коллеги и в продакшене.
Проблема, которую решает Docker, знакома каждому: «у меня на машине работает». Разные версии Node.js, Python, PostgreSQL, разные системные библиотеки — всё это приводит к расхождениям между окружениями. Docker фиксирует окружение целиком: ОС-слой, рантайм, библиотеки, код и конфигурацию.
В этой статье разберём ключевые понятия (образ, контейнер, реестр, Dockerfile), запустим первый контейнер и соберём свой образ для небольшого Node.js-приложения.
Контейнеры vs виртуальные машины
Виртуальная машина эмулирует железо и запускает полноценную гостевую ОС со своим ядром. Это тяжело: гигабайты диска, минуты на загрузку.
Контейнер использует ядро хост-системы и изолируется средствами Linux (namespaces, cgroups). В результате контейнер стартует за миллисекунды, весит десятки мегабайт и потребляет ресурсы как обычный процесс.
Основные понятия
- Образ (image) — неизменяемый шаблон с файловой системой и метаданными запуска.
- Контейнер (container) — запущенный экземпляр образа, добавляющий поверх него writable-слой.
- Реестр (registry) — хранилище образов. Самый известный — Docker Hub.
- Dockerfile — текстовый рецепт сборки образа.
Установка и первый запуск
После установки Docker Desktop (Windows/macOS) или пакета docker-ce (Linux) проверьте версию:
docker --version
docker run hello-world
Команда docker run сделает три вещи: найдёт образ hello-world локально, при отсутствии скачает его из Docker Hub и запустит контейнер. На выходе вы увидите приветственное сообщение — это значит, что демон Docker жив и работает.
Запускаем Nginx за одну команду
Запустим веб-сервер Nginx и пробросим порт 8080 хоста на 80 порт контейнера:
# -d запускает контейнер в фоне
# -p пробрасывает порты host:container
# --name даёт удобное имя
docker run -d -p 8080:80 --name web nginx:alpine
Откройте http://localhost:8080 — увидите стандартную страницу Nginx. Полезные команды для управления:
docker ps # список запущенных контейнеров
docker logs web # логи контейнера
docker stop web # остановить
docker rm web # удалить остановленный контейнер
Собираем свой образ с Dockerfile
Допустим, у нас есть простое Node.js-приложение app.js:
const http = require('http');
// слушаем порт из переменной окружения или 3000 по умолчанию
const port = process.env.PORT || 3000;
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Привет из Docker-контейнера!\n');
}).listen(port);
console.log(`Сервер запущен на порту ${port}`);
Рядом кладём package.json и создаём Dockerfile:
# базовый образ с Node.js 20 на лёгком Alpine Linux
FROM node:20-alpine
# рабочая директория внутри контейнера
WORKDIR /app
# сначала копируем манифесты для эффективного кеша слоёв
COPY package*.json ./
RUN npm ci --omit=dev
# затем копируем исходники приложения
COPY . .
# документируем порт (информационно)
EXPOSE 3000
# команда запуска по умолчанию
CMD ["node", "app.js"]
Собираем и запускаем:
docker build -t my-node-app:1.0 .
docker run -d -p 3000:3000 --name api my-node-app:1.0
curl http://localhost:3000
Порядок инструкций важен
Docker кеширует каждый слой Dockerfile. Если изменился только код, а package.json остался прежним, шаг npm ci будет взят из кеша и сборка займёт секунды. Поэтому сначала копируем манифесты и ставим зависимости, и только затем копируем остальные файлы.
Тома и переменные окружения
Контейнер по умолчанию эфемерный: остановили — данные потеряли. Чтобы данные пережили перезапуск, используют тома (volumes):
# именованный том data сохраняется между перезапусками
docker run -d \
-e POSTGRES_PASSWORD=secret \
-v data:/var/lib/postgresql/data \
-p 5432:5432 \
--name db postgres:16
Флаг -e пробрасывает переменные окружения, -v монтирует том. Для разработки удобно монтировать локальную папку: -v $(pwd):/app — изменения в коде будут видны контейнеру мгновенно.
Частые ошибки
- Запускать всё под root. В Dockerfile добавляйте
USER node(или создавайте своего пользователя) — это базовая защита от эскалации привилегий. - Класть секреты в образ через
ENVилиCOPY .env. Образ можно скачать и распаковать — секреты утекут. Передавайте их при запуске через-e, файлы--env-fileили secret-менеджеры. - Использовать тег
latestв продакшене. Сегодняnode:latest— это 20, завтра — 22, и сборка ломается без предупреждения. Фиксируйте версии:node:20.11-alpine. - Игнорировать
.dockerignore. Без него в контекст сборки попадаютnode_modules,.git, логи и кеши. Образ распухает, сборка замедляется. - Раздутые образы на базе
ubuntuилиnode(без alpine). Альтернативы:alpine,distroless, multi-stage builds. Размер падает с 1 ГБ до 50–100 МБ. - Один контейнер — много процессов. Принцип Docker: один контейнер — одна задача. База, бэкенд и фронтенд — это три разных контейнера, связанных через
docker compose.
Заключение
Docker превращает «работает у меня» в «работает везде». Минимальный набор для старта: понимать разницу между образом и контейнером, уметь писать Dockerfile с правильным порядком слоёв, прокидывать порты и тома, не складывать секреты внутрь образа.
Следующие шаги — освоить docker compose для оркестрации нескольких сервисов, изучить multi-stage сборки для уменьшения размера образа и посмотреть на Kubernetes, когда контейнеров станет больше десятка.






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