Docker Compose Watch: автоматическая синхронизация кода и пересборка контейнеров для разработки — PROG-TIME

Docker Compose Watch: автоматическая синхронизация кода и пересборка контейнеров для разработки

28.05.2026

Если вы разрабатываете приложения внутри 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.