Docker для разработчиков: образы, контейнеры и Compose
Вы клонировали проект, выполнили инструкции по установке и упёрлись в стену: отсутствует системная библиотека, не запускается база данных или не совпадает версия Node. Docker существует, чтобы убрать этот класс проблем. Вместо установки сервисов прямо на машину вы описываете их один раз и запускаете в изолированных контейнерах, которые ведут себя одинаково везде.
Это руководство объясняет три идеи, которые вам действительно нужны: образы, контейнеры и Compose. Всё практично, с командами, которые можно выполнять прямо во время чтения.
Образы и контейнеры: мысленная модель
Образ — это шаблон только для чтения. Он содержит снимок файловой системы плюс метаданные: команду по умолчанию, открытые порты и т. д. Контейнер — это запущенный (или остановленный) экземпляр этого образа с тонким записываемым слоем сверху.
Если вы знакомы с объектно-ориентированным программированием, аналогия почти точная: образ — это класс, контейнер — объект. Из одного образа можно запустить множество контейнеров, а изменения внутри контейнера не меняют образ.
| Понятие | Что это | Типичная команда |
|---|---|---|
| Образ | Неизменяемый шаблон | docker build, docker pull |
| Контейнер | Запущенный экземпляр | docker run, docker ps |
| Том | Постоянное хранилище | docker volume create |
| Compose | Описание нескольких контейнеров | docker compose up |
Ваш первый образ с Dockerfile
Dockerfile — это рецепт. Каждая инструкция создаёт слой, а Docker кэширует слои, поэтому пересборка быстра, когда ничего не изменилось. Вот небольшой пример на Node.js:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]Здесь важны две привычки. Во-первых, копируйте манифесты зависимостей до остального исходного кода, чтобы слой npm ci кэшировался, когда меняется только код приложения. Во-вторых, предпочитайте slim-образы, например alpine, чтобы уменьшить размер и поверхность атаки.
Соберите и запустите:
docker build -t myapp:1.0 .
docker run --rm -p 3000:3000 myapp:1.0Флаг -p 3000:3000 сопоставляет порт 3000 хоста с портом 3000 контейнера. --rm удаляет контейнер после завершения, что поддерживает порядок на машине во время разработки.
Повседневные команды для контейнеров
Этого хватит для большинства ежедневных задач:
docker ps— список запущенных контейнеров; добавьте-a, чтобы включить остановленные.docker logs -f <container>— потоковый вывод приложения.docker exec -it <container> sh— открыть оболочку внутри запущенного контейнера.docker stop <container>— корректная остановка.docker images— список локальных образов и их размеров.docker system prune— удалить неиспользуемые контейнеры, сети и висячие образы.
Когда контейнер сразу завершается, сначала читайте логи. Самые частые причины — неверный CMD, отсутствующая переменная окружения или привязка процесса к 127.0.0.1 вместо 0.0.0.0 внутри контейнера.
Зачем нужен Compose
Настоящему приложению обычно нужно больше одного процесса: API, база данных, возможно, кэш. Запускать три команды docker run с длинными списками флагов — чревато ошибками. 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:
- db_data:/var/lib/postgresql/data
volumes:
db_data:Обратите внимание: API подключается к db, а не к localhost. Compose создаёт приватную сеть, где каждый сервис доступен по своему имени. Это самый частый источник путаницы у новичков.
Запустите всё командой docker compose up или docker compose up -d для работы в фоне. Пересоберите после изменений в коде с помощью docker compose up --build. Остановите и удалите с docker compose down, добавив -v, если также хотите удалить именованные тома.
Тома, bind mounts и live reload
Контейнеры эфемерны; данные внутри них исчезают при удалении. Это решают два механизма:
- Именованные тома — управляются Docker, идеальны для файлов базы данных. Они сохраняются между перезапусками контейнера и объявляются в секции
volumes:. - Bind mounts — монтируют каталог хоста в контейнер, идеальны для исходного кода во время разработки.
services:
api:
build: .
volumes:
- ./src:/app/srcС bind mount редактирование файлов на хосте отражается внутри контейнера. Скомбинируйте это с наблюдателем за файлами (nodemon, uvicorn --reload, air) — и получите live reload без пересборки образа.
Практический рабочий процесс разработки
- Напишите Dockerfile для приложения и держите его минимальным.
- Добавьте
docker-compose.ymlс приложением и его зависимостями. - Используйте bind mounts для исходного кода и именованные тома для данных.
- Не храните секреты в файле; используйте
.envи ссылайтесь на переменные через${VAR}. - Добавьте
.dockerignoreсnode_modules,.gitи артефактами сборки, чтобы ускорить сборку. - Запустите
docker compose up --buildи итерируйте.
Если ваш сервис создаёт логи, которые нужно изучить позже, помните: логи контейнера — это просто текст. Инструменты, разбирающие серверные логи, например анализатор логов nginx, хорошо сочетаются с контейнерами, потому что логи можно перенаправить или скопировать наружу, ничего не устанавливая на хост.
Частые ошибки
- Раздутые образы: используйте multi-stage сборки, чтобы компилировать на одном этапе и копировать в slim runtime-образ только артефакты.
- Пересборка всего: упорядочивайте инструкции Dockerfile от редко меняющихся к часто меняющимся.
- Захардкоженные хосты: внутри Compose используйте имена сервисов; снаружи — переменные окружения.
- Игнорирование очистки: висячие образы и остановленные контейнеры накапливаются. Периодически запускайте
docker system prune. - Запуск от root: добавьте непривилегированного
USERв продакшн-образах.
FAQ
В чём разница между образом и контейнером?
Образ — это неизменяемый шаблон, содержащий файловую систему и метаданные. Контейнер — запущенный экземпляр этого образа с добавленным записываемым слоем. Из одного образа можно запустить множество контейнеров.
Нужен ли Docker Compose для одного сервиса?
Нет. Для одного контейнера достаточно одной команды docker run. Compose становится ценным, когда у вас несколько сервисов, общие сети или переменные окружения, которые хочется объявить в одном месте.
Как контейнеры общаются между собой?
Compose помещает все сервисы в общую сеть, где каждый сервис доступен по своему имени. Используйте это имя как хост, например db:5432, вместо localhost.
Docker вознаграждает небольшие начальные вложения. Как только образы, контейнеры и Compose становятся понятны, подключение коллеги или смена машины перестаёт быть ритуалом настройки и превращается в одну команду.