Repository: docker/compose

Issue:
[BUG] docker compose up --watch with sync+restart does not show logs after restart
### Description

When running `docker compose up --watch`, the logs for the service appear in the output.

When specifying `sync+restart` as the action on a file change, when a file is changed, the container restarts, but there are no more logs outputted. When running `docker compose logs`, you can see the app was restarted correctly and is still logging.

This behaviour does not happen when using `rebuild` as the action (although the logs aren't picked up straight away so the first logs are potentially missed).

Expected behaviour is that when using `sync+restart`, when a file is changed, the logs from the container continue to be shown in the output of `docker compose up --watch`.

### Steps To Reproduce

Dockerfile:
```
FROM node:slim
COPY src src
```
Contents of `src` can be anything.

Docker Compose:
```yml
services:
  app:
    build: .
    command: >-
      bash -ceuo pipefail "
        echo restart
        i=0
        while true; do echo boop $$i && ((i++)) && sleep 1; done
      "
    stop_signal: SIGKILL
    develop:
      watch:
        - action: sync+restart
          path: ./src
          target: /src
```

Running `docker compose up --watch`, then saving a file in `src` outputs:

```sh
➜ docker compose up --watch
[+] Running 1/2
 ✔ Network docker-compose-watch_default  Created                                                                                                                                                              0.1s 
 ⠇ Container docker-compose-watch-app-1  Created                                                                                                                                                              0.8s 
        ⦿ Watch enabled
Attaching to app-1
app-1   | restart
app-1   | boop 0
app-1   | boop 1
app-1   | boop 2
app-1   | boop 3
app-1   | boop 4
app-1   | boop 5
app-1   | boop 6
        ⦿ Syncing "app" after changes were detected
[+] Restarting 1/1
 ✔ Container docker-compose-watch-app-1  Started                                                    0.6s 
app-1 exited with code 0
```

When running `docker compose logs`, it shows all the logs correctly.

```sh
➜ docker compose logs                                                                                                                                                                                              
app-1  | restart                                                                                                                                                                                                   
app-1  | boop 0                                                                                                                                                                                                    
app-1  | boop 1                                                                                                                                                                                                    
app-1  | boop 2                                                                                                                                                                                                    
app-1  | boop 3                                                                                                                                                                                                    
app-1  | boop 4                                                                                                                                                                                                    
app-1  | boop 5                                                                                                                                                                                                    
app-1  | boop 6                                                                                                                                                                                                    
app-1  | restart                                                                                                                                                                                                   
app-1  | boop 0                                                                                                                                                                                                    
app-1  | boop 1                                                                                                                                                                                                    
app-1  | boop 2                                                                                                                                                                                                    
app-1  | boop 3
app-1  | boop 4
app-1  | boop 5
```

If you change `sync+restart` to `rebuild` in the docker-compose file, and run `docker compose up --watch`, and save a file, it now prints (most of) the logs correctly:

```sh
➜ docker compose up --watch
[+] Running 1/2
 ✔ Network docker-compose-watch_default  Created                                                    0.1s 
 ⠧ Container docker-compose-watch-app-1  Created                                                    0.7s 
        ⦿ Watch enabled
Attaching to app-1
app-1   | restart
app-1   | boop 0
app-1   | boop 1
app-1   | boop 2
app-1   | boop 3
app-1   | boop 4
app-1   | boop 5
        ⦿ Rebuilding service "app" after changes were detected...
app-1   | boop 6
        ⦿ service "app" successfully built
app-1 exited with code 0
app-1 has been recreated
app-1   | boop 2
app-1   | boop 3
app-1   | boop 4
app-1   | boop 5
app-1   | boop 6
```
Note that in the output, it misses the first few logs.

Doing a `docker compose logs` only shows the logs for the latest container.

### Compose Version

```Text
➜ docker compose version
Docker Compose version v2.26.1


```sh
➜ docker-compose version
docker-compose version 1.29.2, build 5becea4c
docker-py version: 5.0.0
CPython version: 3.7.10
OpenSSL version: OpenSSL 1.1.0l  10 Sep 2019
```
```


### Docker Environment

```Text
➜ docker info                                                                                                                                                                                                      
Client: Docker Engine - Community                                                                                                                                                                                  
 Version:    26.1.0
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.14.0
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.26.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 1
  Running: 0
  Paused: 0
  Stopped: 1
 Images: 125
 Server Version: 26.1.0
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: e377cd56a71523140ca6ae87e30244719194a521
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 6.5.0-27-generic
 Operating System: Ubuntu 22.04.4 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 8
 Total Memory: 15.31GiB
 Name: --
 ID: QBED:UL4T:4VZM:D6KX:NMXE:WYFE:6DDN:PK4Y:CEGA:C5IZ:WBPG:JZES
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
```


### Anything else?

[Feature introduction and demo](https://github.com/docker/compose/pull/11525)

Discussion:
@jbne: Similar behavior happens when sending a `docker compose up --watch` job to the background and watching the output with `docker compose logs --follow` in another command window. The service logs show up until the service restart occurs, after which the logs command exits with code 0. One might expect `docker compose logs --follow` to show the sync/restart messages along with the rest of the output from the restarted (or terminated and started again) service.@dkaste: I think this happens when the number of running services drops to zero, even if temporarily for a restart. For example, if you start two services but one exits due to an error, this issue will occur when the other one is restarted.

I was able to work around this issue for now by keeping a dummy service alive at all times: 

```
dummy-watch-workaround:
  image: alpine:latest
  init: true
  command: ["sh", "-c", "while true; do sleep 2; done"]
```@Hellzed: @dkaste I confirm. Let's say I have a `db` and a `web` service, the `web` service being the one I want to watch in `sync+restart` mode.

Just running `docker compose up --watch web` ends up with the terminal detaching from the service with `web-1 exited with code 0`.

But running `docker compose up --watch db web` behaves correctly.
@torarnv: I'm still seeing this with `2.30.3`

```
❯ docker compose up --watch
[+] Running 1/0
 ✔ Container caddy-caddy-1  Running                                                        0.0s
        ⦿ Watch enabled
Attaching to caddy-1
         ⦿ Syncing and restarting service "caddy" after 1 changes were detected
caddy-1  | 2024/11/23 00:42:30.609	INFO	shutting down apps, then terminating	{"signal": "SIGTERM"}
caddy-1  | 2024/11/23 00:42:30.609	WARN	exiting; byeee!! 👋	{"signal": "SIGTERM"}
caddy-1  | 2024/11/23 00:42:30.609	INFO	http	servers shutting down with eternal grace period
caddy-1  | 2024/11/23 00:42:30.609	INFO	admin	stopped previous server	{"address": "localhost:2019"}
caddy-1  | 2024/11/23 00:42:30.609	INFO	shutdown complete	{"signal": "SIGTERM", "exit_code": 0}
         ⦿ service "caddy" restarted
caddy-1 exited with code 0
         ⦿ Syncing and restarting service "caddy" after 1 changes were detected
caddy-1  | 2024/11/23 00:42:39.434	INFO	shutting down apps, then terminating	{"signal": "SIGTERM"}
caddy-1  | 2024/11/23 00:42:39.434	WARN	exiting; byeee!! 👋	{"signal": "SIGTERM"}
caddy-1  | 2024/11/23 00:42:39.434	INFO	http	servers shutting down with eternal grace period
caddy-1  | 2024/11/23 00:42:39.434	INFO	admin	stopped previous server	{"address": "localhost:2019"}
caddy-1  | 2024/11/23 00:42:39.434	INFO	shutdown complete	{"signal": "SIGTERM", "exit_code": 0}
caddy-1 exited with code 0
         ⦿ Watch disabled

❯
```

After one or two changes to the Caddyfile, the service is restarted, and keeps running, but the watch disconnects.

```
services:
  caddy:
    image: torarnv/caddy
    build: .
    restart: unless-stopped
    ports:
      - "8080:80"

    develop:
      watch:
        - action: sync+restart
          path: ./Caddyfile
          target: /etc/caddy/Caddyfile

```@torarnv: Can someone please re-open this?@Tarasovych: Same issue, occurrs from time to time after container recreation

Solution patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 41dd3d02..3810add7 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -19,6 +19,5 @@ updates:
       - dependency-name: "github.com/containerd/containerd"
         # containerd major/minor must be kept in sync with moby
         update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
-      - dependency-name: "go.opentelemetry.io/otel/*"
-        # OTEL is v1.x but has some parts that are not API stable yet
-        update-types: [ "version-update:semver-major", "version-update:semver-minor"]
+        # OTEL dependencies should be upgraded in sync with engine, cli, buildkit and buildx projects
+      - dependency-name: "go.opentelemetry.io/*"
diff --git a/Dockerfile b/Dockerfile
index bb6ed8f2..58eaa568 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-ARG GO_VERSION=1.21.11
+ARG GO_VERSION=1.21.12
 ARG XX_VERSION=1.2.1
 ARG GOLANGCI_LINT_VERSION=v1.55.2
 ARG ADDLICENSE_VERSION=v1.0.0
diff --git a/go.mod b/go.mod
index e6ad3ef6..18be6a4f 100644
--- a/go.mod
+++ b/go.mod
@@ -14,10 +14,10 @@ require (
 	github.com/containerd/platforms v0.2.1
 	github.com/davecgh/go-spew v1.1.1
 	github.com/distribution/reference v0.6.0
-	github.com/docker/buildx v0.16.0
-	github.com/docker/cli v27.1.0+incompatible
+	github.com/docker/buildx v0.16.1
+	github.com/docker/cli v27.1.1+incompatible
 	github.com/docker/cli-docs-tool v0.8.0
-	github.com/docker/docker v27.1.0+incompatible
+	github.com/docker/docker v27.1.1+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/docker/go-units v0.5.0
 	github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
@@ -55,7 +55,7 @@ require (
 	go.uber.org/mock v0.4.0
 	golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
 	golang.org/x/sync v0.7.0
-	golang.org/x/sys v0.21.0
+	golang.org/x/sys v0.22.0
 	google.golang.org/grpc v1.60.1
 	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.5.1
@@ -103,7 +103,7 @@ require (
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
-	github.com/gofrs/flock v0.12.0 // indirect
+	github.com/gofrs/flock v0.12.1 // indirect
 	github.com/gogo/googleapis v1.4.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
diff --git a/go.sum b/go.sum
index 3cb676c4..159258f7 100644
--- a/go.sum
+++ b/go.sum
@@ -126,17 +126,17 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/buildx v0.16.0 h1:LurEflyb6BBoLtDwJY1dw9dLHKzEgGvCjAz67QI0xO0=
-github.com/docker/buildx v0.16.0/go.mod h1:4xduW7BOJ2B11AyORKZFDKjF6Vcb4EgTYnV2nunxv9I=
-github.com/docker/cli v27.1.0+incompatible h1:P0KSYmPtNbmx59wHZvG6+rjivhKDRA1BvvWM0f5DgHc=
-github.com/docker/cli v27.1.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/buildx v0.16.1 h1:Nl0gQAUaREjD6G+hYnwUghVmhHpi8LHMaLIQzGCstYo=
+github.com/docker/buildx v0.16.1/go.mod h1:4xduW7BOJ2B11AyORKZFDKjF6Vcb4EgTYnV2nunxv9I=
+github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
+github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli-docs-tool v0.8.0 h1:YcDWl7rQJC3lJ7WVZRwSs3bc9nka97QLWfyJQli8yJU=
 github.com/docker/cli-docs-tool v0.8.0/go.mod h1:8TQQ3E7mOXoYUs811LiPdUnAhXrcVsBIrW21a5pUbdk=
 github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
-github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
+github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
 github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
 github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@@ -190,8 +190,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
 github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
-github.com/gofrs/flock v0.12.0 h1:xHW8t8GPAiGtqz7KxiSqfOEXwpOaqhpYZrTE2MQBgXY=
-github.com/gofrs/flock v0.12.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc=
+github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
+github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
 github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
 github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -585,8 +585,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
diff --git a/pkg/compose/start.go b/pkg/compose/start.go
index 971c7a02..7caeb305 100644
--- a/pkg/compose/start.go
+++ b/pkg/compose/start.go
@@ -257,6 +257,11 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
 			service := container.Labels[api.ServiceLabel]
 			switch event.Status {
 			case "stop":
+				if inspected.State.Running {
+					// on sync+restart action the container stops -> dies -> start -> restart
+					// we do not want to stop the current container, we want to restart it
+					return nil
+				}
 				if _, ok := watched[container.ID]; ok {
 					eType := api.ContainerEventStopped
 					if utils.Contains(replaced, container.ID) {
@@ -279,6 +284,11 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
 				watched[container.ID] = restarted + 1
 				// Container terminated.
 				willRestart := inspected.State.Restarting
+				if inspected.State.Running {
+					// on sync+restart action inspected.State.Restarting is false,
+					// however the container is already running before it restarts
+					willRestart = true
+				}
 
 				eType := api.ContainerEventExit
 				if utils.Contains(replaced, container.ID) {



Test patch:
diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go
index 677b080a..1f7106bb 100644
--- a/pkg/e2e/watch_test.go
+++ b/pkg/e2e/watch_test.go
@@ -34,7 +34,6 @@ import (
 )
 
 func TestWatch(t *testing.T) {
-	t.Skip("Skipping watch tests until we can figure out why they are flaky/failing")
 
 	services := []string{"alpine", "busybox", "debian"}
 	for _, svcName := range services {