Если вы разрабатываете приложения внутри Docker-контейнеров, наверняка сталкивались с типичной проблемой: меняете код локально, а изменения в контейнере не видны до тех пор, пока вы не пересоберёте образ или не настроите громоздкие volume-биндинги вместе с nodemon, php-fpm reload или подобными инструментами. Начиная с версии Docker Compose 2.22 у разработчиков появился штатный механизм для решения этой задачи — Compose Watch.
В этой статье разберёмся, как работает docker compose watch, какие у него есть режимы синхронизации, и соберём практические примеры конфигурации для Laravel и Node.js проектов.
Зачем нужен Compose Watch
Классический подход к разработке в Docker — пробрасывать локальную директорию с кодом в контейнер через volumes:
services:
app:
build: .
volumes:
- ./src:/var/www/html
Такая схема работает, но у неё есть нюансы:
- На macOS и Windows проброс файлов через виртуальную машину Docker Desktop ощутимо медленный, особенно для проектов с большим количеством мелких файлов (
node_modules,vendor). - Не все изменения подхватываются «на лету»: если меняются файлы зависимостей, нужно вручную пересобирать контейнер.
- Для перезапуска сервисов после обновления конфигов приходится писать дополнительные скрипты или хелперы.
Compose Watch решает эти задачи декларативно. Вы описываете в compose.yaml, какие пути отслеживать и что делать при их изменении: синхронизировать файлы внутрь контейнера, перезапустить сервис или пересобрать образ.
Три режима работы
В блоке develop.watch доступно три значения action:
- sync — копирует изменённые файлы прямо в работающий контейнер без перезапуска. Идеальный вариант для исходного кода: HTML, CSS, JS, PHP, Python.
- sync+restart — копирует файлы и перезапускает сервис. Подходит для конфигов, при изменении которых процесс должен подняться заново (например,
nginx.confили.env). - rebuild — пересобирает образ целиком. Незаменим для файлов зависимостей (
composer.json,package.json,requirements.txt) или дляDockerfile.
Пример для Laravel-проекта
Допустим, у вас классическая Laravel-сборка с PHP-FPM, Nginx и Node.js для сборки фронтенда через Vite. Конфигурация compose.yaml может выглядеть так:
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
volumes:
- app-storage:/var/www/html/storage
develop:
watch:
- action: sync
path: ./app
target: /var/www/html/app
- action: sync
path: ./routes
target: /var/www/html/routes
- action: sync
path: ./resources
target: /var/www/html/resources
- action: sync+restart
path: ./config
target: /var/www/html/config
- action: sync+restart
path: ./.env
target: /var/www/html/.env
- action: rebuild
path: ./composer.json
- action: rebuild
path: ./composer.lock
nginx:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
develop:
watch:
- action: sync+restart
path: ./docker/nginx/default.conf
target: /etc/nginx/conf.d/default.conf
vite:
image: node:22-alpine
working_dir: /app
command: sh -c "npm ci && npm run dev"
ports:
- "5173:5173"
develop:
watch:
- action: sync
path: ./resources
target: /app/resources
- action: rebuild
path: ./package.json
- action: rebuild
path: ./package-lock.json
volumes:
app-storage:
Запуск выглядит просто:
docker compose up -d
docker compose watch
После этого любые изменения в app/, routes/ или resources/ моментально синхронизируются в контейнер PHP-FPM. Если вы поправите config/database.php, Compose перезапустит сервис, чтобы Laravel подхватил новые настройки. А обновление composer.json запустит пересборку образа.
Пример для Node.js
Для Node-приложений (Express, NestJS, Next.js) подход аналогичный. Главное — не синхронизировать node_modules с хоста: это и медленно, и часто ломается из-за разницы в платформах.
services:
api:
build: .
command: npm run dev
ports:
- "3000:3000"
develop:
watch:
- action: sync
path: ./src
target: /app/src
ignore:
- node_modules/
- action: sync+restart
path: ./.env
target: /app/.env
- action: rebuild
path: ./package.json
- action: rebuild
path: ./package-lock.json
Параметр ignore позволяет исключить из синхронизации тяжёлые директории. Это особенно важно для проектов с большим количеством зависимостей.
Тонкости и подводные камни
Хотя Compose Watch выглядит как «серебряная пуля», на практике стоит учитывать несколько моментов.
Watch не заменяет volumes полностью. Если вашему приложению нужно писать файлы внутри контейнера и эти изменения должны быть доступны снаружи (например, кэш Laravel или загруженные пользователем файлы), используйте именованные volume для этих директорий, как в примере выше с app-storage.
Watch работает только в режиме разработки. Блок develop игнорируется при обычном docker compose up. Watch активируется отдельной командой docker compose watch или флагом --watch у up:
docker compose up --watch
Sync — это однонаправленная операция. Файлы копируются с хоста в контейнер, но не наоборот. Если вы запускаете внутри контейнера команды, которые создают новые файлы (например, миграции Laravel или генераторы NestJS), потребуется либо использовать volume, либо вытащить файл наружу через docker cp.
Игнорируйте лишнее. По умолчанию Compose читает .dockerignore, но дополнительно можно задать ignore в самом блоке watch. Это уменьшает количество событий файловой системы и снижает нагрузку на CPU.
На больших проектах используйте rebuild осознанно. Если в watch попадает composer.lock или package-lock.json, каждое изменение приведёт к полной пересборке образа. Это нормально, но будьте готовы к 30–60 секундам ожидания.
Сравнение с привычными инструментами
До появления Compose Watch разработчики использовали разные обходные пути: docker-sync, mutagen, монтирование через NFS, :cached и :delegated флаги для volume на macOS. Все эти решения работали, но требовали отдельной настройки и нередко плохо дружили с CI.
Compose Watch встроен в Docker Compose, не требует дополнительных утилит и одинаково работает на Linux, macOS и Windows. По скорости синхронизация быстрее, чем bind mount на macOS, потому что файлы передаются через внутренний канал Docker Engine, минуя файловую систему виртуальной машины.
Заключение
Compose Watch — это маленькая, но очень удобная фича, которая делает локальную разработку в Docker заметно приятнее. Декларативный синтаксис, три понятных режима работы и встроенная поддержка в Docker Compose делают watch отличной заменой для разрозненных хаков с volumes и nodemon. Если вы ещё не пробовали — обновите Docker Desktop или Compose CLI до версии 2.22+ и добавьте блок develop.watch в свой compose.yaml. Высока вероятность, что после этого вы больше не вернётесь к старой схеме.
В следующих статьях разберём, как объединить Compose Watch с healthcheck (которому была посвящена отдельная статья) и как организовать профили окружений для одной и той же сборки: dev, staging и production.