Docker Run в Compose: маппинг флагов, override-файлы и подводные камни
Документация всегда показывает быстрый путь: 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:80 | ports: ["8080:80"] | host:container; строковая форма защищает от YAML-парсинга восьмеричных портов |
-v ./data:/var/lib/data | volumes: ["./data:/var/lib/data"] | Bind mount; относительные пути разрешаются от расположения compose-файла |
-v myvolume:/var/lib/data | volumes: ["myvolume:/var/lib/data"] + верхнеуровневый volumes: myvolume: | Именованный том; нужно объявить в секции volumes верхнего уровня |
--network mynet | networks: [mynet] + верхнеуровневый networks: mynet: | Пользовательская сеть; объявить на верхнем уровне |
--restart unless-stopped | restart: unless-stopped | Значения: no, always, on-failure, unless-stopped |
-e DB_USER=app | environment: DB_USER: app | Или списком: - DB_USER=app |
--name postgres | container_name: postgres | Не используйте в продакшене — фиксированные имена ломают масштабирование --scale |
--rm | Прямого аналога нет | Compose удаляет контейнеры при down; для разовых команд run --rm работает как обычно |
-u 1000:1000 | user: "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 up | Compose v1 (Python) | End-of-life с июля 2023. Не использовать. |
docker compose up | Compose 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не ждёт готовности по умолчанию. Без условия healthcheckdepends_onждёт только запуска контейнера, а не готовности БД принимать соединения. Добавьтеhealthcheckзависимому сервису и используйтеcondition: service_healthy.
Переводите любую команду docker run в compose.yaml с помощью Конвертера Docker Compose — вставьте команду и получите валидный YAML в один клик, без регистрации.