Skip to content

[BUG] watch rebuilds in an endless loop when the watched path contains .git #14362

Open

Description

@htoyoda18

Description

With a rebuild watch rule whose path contains the repository's .git directory (for example path: . in a project at the repository root), a single source edit makes docker compose watch rebuild over and over: about 30 rebuilds in 30 seconds, and it never stops. An empty git commit starts the same loop. With a sync rule, git operations sync the .git contents into the container.

.git/ is meant to be always ignored (pkg/compose/watch.go adds a hardcoded .git/ ignore rule), but that rule never matches inside the project. Every build then feeds the loop: buildx reads the git state of the build context for provenance and updates .git while doing so, and watch sees that as a new change.

Expected: changes under .git/ are ignored, so one edit triggers one rebuild.

Workaround: add .git to .dockerignore, or set BUILDX_GIT_INFO=0.

Expected: changes under .git/ inside the watched path are ignored.

Steps To Reproduce

# Dockerfile
FROM alpine
COPY app.txt /app.txt
CMD ["sleep", "infinity"]
# compose.yaml
services:
  app:
    build: .
    init: true
    develop:
      watch:
        - action: rebuild
          path: .
$ echo hi > app.txt
$ git init -q && git add . && git commit -qm init
$ docker compose up -d --build
$ docker compose watch --no-up > /tmp/watch.log 2>&1 &   # log outside the watched path
$ echo x >> app.txt                                      # one edit
$ sleep 30; grep -c Rebuilding /tmp/watch.log
32

With nothing edited, the count stays at 0. With .git in .dockerignore or BUILDX_GIT_INFO=0, one edit gives 1.

Compose Version

Docker Compose version 5.0.1
Also reproduced on main (289d8a037).
buildx v0.30.1-desktop.1 (also v0.24.0)

Docker Environment

Docker Engine 29.1.3 (linux/arm64, Docker Desktop on macOS)

Anything else?

The rule is built with watch.NewDockerPatternMatcher("/", []string{".git/"}) in pkg/compose/watch.go (two places). Patterns are joined to the base path, so this becomes /.git/ and only matches a .git at the filesystem root. Events carry full paths such as /home/me/project/.git/index, so they never match.

With **/.git instead, a local build of main stops the loop: one edit gives 1 rebuild, an empty commit gives 0, and .git is no longer synced. **/.git matches .git and its contents at any depth, but not .gitignore or .github/.

#14217 changes the same ignore set (it excludes the Dockerfile and compose files) but does not touch .git.

Activity

  1. changed the title [-][BUG] `watch` syncs `.git/` changes even though `.git` is meant to be ignored[/-] [+][BUG] `watch` rebuilds in an endless loop when the watched path contains `.git`[/+] on Oct 19, 2026
  2. htoyoda18 commented on Oct 19, 2026

    @htoyoda18
    ContributorAuthor

    A note on how this was found, in case it helps triage.

    At first I thought this was only a missing filter: .git changes were not ignored, so watch would sync them or trigger an extra rebuild now and then. Wasteful, but harmless.

    Looking further, I found a worse problem with the rebuild action. The extra rebuild does not stop at one. Each build makes buildx update .git while it reads the git state for provenance, watch picks that up as a new change, and the cycle repeats. So a single edit, or even an empty commit, never settles. Disabling buildx's git info (BUILDX_GIT_INFO=0) or ignoring .git stops it, which points to the unmatched .git/ rule as the root cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions