DevToys Web Pro iconDevToys Web ProБлог
Переведено с помощью LocalePack logoLocalePack
Оцените нас:
Попробуйте расширение для браузера:
← Назад к блогу

Docker Run в Compose: маппинг флагов, override-файлы и подводные камни

9 мин чтения

Документация всегда показывает быстрый путь: docker run -p 5432:5432 -v ./data:/var/lib/postgresql/data postgres:16. Для разовой проверки это работает, но как только нужен второй сервис, передача коллеге или фиксация в репозитории — одна длинная команда перестаёт справляться. Compose превращает её в декларативный YAML-файл, который читают контроль версий, CI и каждый разработчик в команде. Воспользуйтесь Конвертером Docker Compose, чтобы мгновенно перевести любую команду docker run.

Маппинг флагов в compose.yaml

Каждый флаг docker run имеет прямой аналог в compose.yaml. Таблица ниже охватывает флаги, которые встречаются почти в каждом проекте:

Флаг docker runПоле compose.yamlПримечания
-p 8080:80ports: ["8080:80"]host:container; строковая форма защищает от YAML-парсинга восьмеричных портов
-v ./data:/var/lib/datavolumes: ["./data:/var/lib/data"]Bind mount; относительные пути разрешаются от расположения compose-файла
-v myvolume:/var/lib/datavolumes: ["myvolume:/var/lib/data"] + верхнеуровневый volumes: myvolume:Именованный том; нужно объявить в секции volumes верхнего уровня
--network mynetnetworks: [mynet] + верхнеуровневый networks: mynet:Пользовательская сеть; объявить на верхнем уровне
--restart unless-stoppedrestart: unless-stoppedЗначения: no, always, on-failure, unless-stopped
-e DB_USER=appenvironment: DB_USER: appИли списком: - DB_USER=app
--name postgrescontainer_name: postgresНе используйте в продакшене — фиксированные имена ломают масштабирование --scale
--rmПрямого аналога нетCompose удаляет контейнеры при down; для разовых команд run --rm работает как обычно
-u 1000:1000user: "1000:1000"Значение в кавычках — YAML иначе интерпретирует числа
--link app:appИспользуйте networks--link устарел; сервисы в одной сети разрешают друг друга по имени сервиса
--health-cmd "pg_isready"healthcheck: test: ["CMD", "pg_isready"]Дополните interval, timeout, retries

До и после: Postgres + приложение

Вот две типичные команды docker run, которые можно встретить в README — для Postgres и для веб-приложения, которое к нему подключается:

docker run -d \
  --name postgres \
  -p 5432:5432 \
  -e POSTGRES_USER=app \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=appdb \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  postgres:16-alpine

docker run -d \
  --name web \
  -p 3000:3000 \
  -e DATABASE_URL=postgres://app:secret@postgres:5432/appdb \
  --link postgres:postgres \
  --restart unless-stopped \
  myapp:latest

Эквивалентный compose.yaml:

services:
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "app"]
      interval: 10s
      timeout: 5s
      retries: 5

  web:
    image: myapp:latest
    container_name: web
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@postgres:5432/appdb
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  pgdata:

Обратите внимание: --link postgres:postgres исчез. Сервисы в одной сети Compose находят друг друга по имени сервиса (postgres) автоматически. depends_on с condition: service_healthy заменяет старый паттерн с sleep перед стартом приложения.

Почему Compose лучше длинных команд run

  • Несколько сервисов: один docker compose up запускает всё в порядке зависимостей. Никаких shell-скриптов с последовательными docker run.
  • Воспроизводимость: файл — единственный источник истины. Новый член команды клонирует репозиторий и запускает одну команду.
  • Версионирование: изменения портов, переменных окружения и монтирования томов отслеживаются в git с диффами и blame.
  • Меньше нажатий: docker compose up -d вместо команды с 10 флагами, которую приходится копировать с wiki.
  • Читаемость: YAML с именами сервисов и комментариями куда понятнее однострочника в шелле для нового коллеги.

Пиннинг версий

Самая распространённая причина «работало на прошлой неделе» — image: postgres:latest. Тег :latest нестабилен: CI тянет другой образ, чем ваш ноутбук, а мажорное обновление незаметно ломает миграции схемы.

Зафиксируйте конкретную минорную или патч-версию:

# Избегайте
image: postgres:latest

# Хорошо — минорная версия
image: postgres:16-alpine

# Лучше — точная версия (для supply-chain безопасности)
image: postgres:16.3-alpine3.20

Практическая стратегия: фиксируйте минорную версию в compose.yaml (например, postgres:16-alpine) и обновляйте осознанно — меняете тег, прогоняете тесты. Точный дайджест используйте только в продакшене или для образов с требованиями к безопасности цепочки поставок.

Тома: bind mount против именованного тома

Два синтаксиса томов выглядят похоже, но ведут себя по-разному:

services:
  db:
    image: postgres:16-alpine
    volumes:
      # Bind mount — отображает путь на хосте в контейнер
      # Подходит для: исходного кода, конфиг-файлов, которые редактируете локально
      - ./data:/var/lib/postgresql/data

      # Именованный том — Docker управляет расположением данных
      # Подходит для: данных БД, всего, что должно пережить пересборку контейнера
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:  # именованный том нужно объявить здесь

Bind mount используйте для файлов, которые хотите редактировать на хосте и сразу видеть изменения внутри контейнера — конфиги, исходный код в разработке. Именованные тома — для данных БД и всего, что должно сохраняться после docker compose down без привязки к конкретному пути на хосте. Именованные тома выживают после down, но удаляются при docker compose down -v.

Сети

Compose создаёт дефолтную bridge-сеть для каждого проекта. Все сервисы в одном compose.yaml автоматически подключаются к ней и резолвят друг друга по имени сервиса. --link не нужен.

Пользовательские сети нужны для изоляции коммуникации или связи сервисов из разных compose-файлов:

services:
  web:
    image: myapp:latest
    networks:
      - frontend
      - backend

  api:
    image: myapi:latest
    networks:
      - backend

  postgres:
    image: postgres:16-alpine
    networks:
      - backend

networks:
  frontend:
  backend:

Здесь web может обращаться и к api, и к postgres, но postgres не может инициировать соединения с web — он не в сети frontend. Это воспроизводит реальную границу безопасности без настройки файрвола.

На macOS network_mode: host работает иначе, чем на Linux. Docker Desktop запускает контейнеры внутри Linux-VM, поэтому host-сеть — это сеть VM, а не вашего Mac. Используйте явные маппинги портов через ports.

Переменные окружения

Три способа передачи переменных окружения в Compose — от простого к гибкому:

services:
  web:
    image: myapp:latest

    # 1. Inline — подходит для несекретной конфигурации
    environment:
      NODE_ENV: production
      PORT: "3000"

    # 2. env_file — секреты вне compose.yaml
    env_file:
      - .env

    # 3. Интерполяция из шелла или .env-файла в корне проекта
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@postgres:5432/appdb

Файл .env в той же папке, что и compose.yaml, загружается автоматически для интерполяции переменных. Это отличается от env_file, который передаёт переменные напрямую в контейнер. Разница важна: переменные интерполяции разрешаются во время парсинга compose; переменные env_file передаются запущенному контейнеру.

Никогда не коммитьте .env с реальными учётными данными. Добавьте .env в .gitignore и зафиксируйте .env.example с заглушками.

Override-файлы

Compose объединяет несколько файлов при передаче через -f. Канонический паттерн — базовый файл плюс переопределения для каждого окружения:

# compose.yaml — база, хранится в git
services:
  web:
    image: myapp:latest
    ports:
      - "3000:3000"

  postgres:
    image: postgres:16-alpine
# compose.override.yaml — переопределения для разработки, тоже в git
# Compose загружает его автоматически вместе с compose.yaml
services:
  web:
    build: .          # в dev собираем из исходников, а не тянем образ
    volumes:
      - .:/app        # живая перезагрузка: исходник монтируется в контейнер
    environment:
      NODE_ENV: development
# compose.prod.yaml — переопределения для продакшена, используется с -f
services:
  web:
    environment:
      NODE_ENV: production
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure
# Разработка (compose.yaml + compose.override.yaml загружаются автоматически)
docker compose up -d

# Продакшен
docker compose -f compose.yaml -f compose.prod.yaml up -d

# Стейджинг
docker compose -f compose.yaml -f compose.staging.yaml up -d

Паттерн полностью разделяет dev-окружение (монтирование исходников, отладочные порты) и продакшен без дублирования всего описания сервиса.

Compose v2 против Compose v1

В старой документации встречаются две разные команды:

КомандаВерсияСтатус
docker-compose upCompose v1 (Python)End-of-life с июля 2023. Не использовать.
docker compose upCompose v2 (Go-плагин)Актуальная версия. Встроена в Docker Desktop и Docker Engine.

Дефис (docker-compose) — устаревший Python-бинарник. Пробел (docker compose) — v2-плагин, встроенный в Docker CLI. В большинстве случаев они читают одинаковый YAML, но v2 добавил depends_on: condition: service_healthy, профили и имя файла compose.yaml (v1 искал только docker-compose.yml). Всегда используйте docker compose.

Подводные камни

  • docker compose up не пересобирает образ при изменении кода. Если вы изменили Dockerfile или исходники, нужно запустить docker compose up --build или предварительно docker compose build. Иначе Compose использует кешированный образ.
  • restart: always против restart: unless-stopped. С always контейнер перезапускается даже после ручной остановки через docker compose stop. Используйте unless-stopped — тогда намеренная остановка сохраняется до следующего перезапуска демона.
  • Host networking на macOS. network_mode: "host" не даёт доступа к хостовой сети на macOS и Windows — Docker Desktop запускает контейнеры внутри Linux-VM. Явно пробрасывайте порты через ports.
  • Секреты в compose.yaml. Хардкоженные пароли в блоке environment попадают в историю git. Используйте env_file с игнорируемым .env или Docker Secrets для Swarm-деплоя в продакшене.
  • Именованный том не объявлен на верхнем уровне. Если сослаться на именованный том в сервисе, но не объявить его в верхнеуровневой секции volumes: — будет ошибка парсинга. Bind mount этого не требует.
  • depends_on не ждёт готовности по умолчанию. Без условия healthcheck depends_on ждёт только запуска контейнера, а не готовности БД принимать соединения. Добавьте healthcheck зависимому сервису и используйте condition: service_healthy.

Переводите любую команду docker run в compose.yaml с помощью Конвертера Docker Compose — вставьте команду и получите валидный YAML в один клик, без регистрации.