Docker для разработчиков: образы, контейнеры и Compose

DevOps2026-10-12TryQuickToolBox

Вы клонировали проект, выполнили инструкции по установке и упёрлись в стену: отсутствует системная библиотека, не запускается база данных или не совпадает версия 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 удаляет контейнер после завершения, что поддерживает порядок на машине во время разработки.

Повседневные команды для контейнеров

Этого хватит для большинства ежедневных задач:

Когда контейнер сразу завершается, сначала читайте логи. Самые частые причины — неверный 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

Контейнеры эфемерны; данные внутри них исчезают при удалении. Это решают два механизма:

  1. Именованные тома — управляются Docker, идеальны для файлов базы данных. Они сохраняются между перезапусками контейнера и объявляются в секции volumes:.
  2. Bind mounts — монтируют каталог хоста в контейнер, идеальны для исходного кода во время разработки.
services:
  api:
    build: .
    volumes:
      - ./src:/app/src

С bind mount редактирование файлов на хосте отражается внутри контейнера. Скомбинируйте это с наблюдателем за файлами (nodemon, uvicorn --reload, air) — и получите live reload без пересборки образа.

Практический рабочий процесс разработки

  1. Напишите Dockerfile для приложения и держите его минимальным.
  2. Добавьте docker-compose.yml с приложением и его зависимостями.
  3. Используйте bind mounts для исходного кода и именованные тома для данных.
  4. Не храните секреты в файле; используйте .env и ссылайтесь на переменные через ${VAR}.
  5. Добавьте .dockerignore с node_modules, .git и артефактами сборки, чтобы ускорить сборку.
  6. Запустите docker compose up --build и итерируйте.

Если ваш сервис создаёт логи, которые нужно изучить позже, помните: логи контейнера — это просто текст. Инструменты, разбирающие серверные логи, например анализатор логов nginx, хорошо сочетаются с контейнерами, потому что логи можно перенаправить или скопировать наружу, ничего не устанавливая на хост.

Частые ошибки

FAQ

В чём разница между образом и контейнером?

Образ — это неизменяемый шаблон, содержащий файловую систему и метаданные. Контейнер — запущенный экземпляр этого образа с добавленным записываемым слоем. Из одного образа можно запустить множество контейнеров.

Нужен ли Docker Compose для одного сервиса?

Нет. Для одного контейнера достаточно одной команды docker run. Compose становится ценным, когда у вас несколько сервисов, общие сети или переменные окружения, которые хочется объявить в одном месте.

Как контейнеры общаются между собой?

Compose помещает все сервисы в общую сеть, где каждый сервис доступен по своему имени. Используйте это имя как хост, например db:5432, вместо localhost.

Docker вознаграждает небольшие начальные вложения. Как только образы, контейнеры и Compose становятся понятны, подключение коллеги или смена машины перестаёт быть ритуалом настройки и превращается в одну команду.