Repository: docker/compose

Issue:
[BUG] crashes while parsing when using docker compose secrets and `USER` in Dockerfile
### Description

Crash output:
strconv.Atoi: parsing "postgres": invalid syntax

The bug was introduced in v2.40.1 It didn't occur in v2.40.0

I think this was introduced in #13288 by @ndeloof .

The bug occurs when using docker compose secrets with a Dockerfile that contains the `USER` instruction.

### Steps To Reproduce

1. Create a file named `Dockerfile` with the following contents (you can leave the comment as it is):
```Dockerfile
FROM docker.io/postgres:18
USER root
# RUN apt install some stuff
USER postgres
```
2. Create a `compose.yaml` file with the following contents:
```yaml
services:
  db:
    build:
      context: .
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    secrets:
      - postgres_password

secrets:
  postgres_password:
    environment: POSTGRES_PASSWORD
```
3. Create a .env file with the following contents:
```
POSTGRES_PASSWORD=secret
```
4. Run `docker compose up`

You should see the error `strconv.Atoi: parsing "postgres": invalid syntax`

### Compose Version

```Text
Docker Compose version 2.40.1
```

### Docker Environment

```Text
Client:
 Version:    28.5.1
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  0.29.1
    Path:     /usr/lib/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  2.40.1
    Path:     /usr/lib/docker/cli-plugins/docker-compose

Server:
 Containers: 2
  Running: 0
  Paused: 0
  Stopped: 2
 Images: 14
 Server Version: 28.5.1
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: true
  Native Overlay Diff: false
  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
 CDI spec directories:
  /etc/cdi
  /var/run/cdi
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 75cb2b7193e4e490e9fbdc236c0e811ccaba3376.m
 runc version: 
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 6.17.3-arch2-1
 Operating System: Arch Linux
 OSType: linux
 Architecture: x86_64
 CPUs: 16
 Total Memory: 15.39GiB
 Name: Archie
 ID: 4f8ab52e-4360-4ccd-a597-046a27df2299
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  ::1/128
  127.0.0.0/8
 Live Restore Enabled: false
```

### Anything else?

_No response_

Discussion:
@ndeloof: diagnostic : containerInspect returns `Config.User` = `root`, not actual UID:GID 🥲
I'll check if we have an alternative, otherwise will have to revert https://github.com/docker/compose/pull/13288@thaJeztah: Yes this is probably similar to what I had to fix in `copy` - it's a tricky one though, because the lookup has to happen within the container filesystem, and depending on how users are defined, even within the container's namespace (nss lookups)

- https://github.com/moby/moby/pull/34143


Solution patch:
diff --git a/pkg/compose/secrets.go b/pkg/compose/secrets.go
index 42ccd4e7..72bc4b5c 100644
--- a/pkg/compose/secrets.go
+++ b/pkg/compose/secrets.go
@@ -22,7 +22,6 @@ import (
 	"context"
 	"fmt"
 	"strconv"
-	"strings"
 	"time"
 
 	"github.com/compose-spec/compose-go/v2/types"
@@ -46,7 +45,6 @@ func (s *composeService) injectConfigs(ctx context.Context, project *types.Proje
 
 func (s *composeService) injectFileReferences(ctx context.Context, project *types.Project, service types.ServiceConfig, id string, mountType mountType) error {
 	mounts, sources := s.getFilesAndMap(project, service, mountType)
-	var ctrConfig *container.Config
 
 	for _, mount := range mounts {
 		content, err := s.resolveFileContent(project, sources[mount.Source], mountType)
@@ -63,11 +61,6 @@ func (s *composeService) injectFileReferences(ctx context.Context, project *type
 
 		s.setDefaultTarget(&mount, mountType)
 
-		ctrConfig, err = s.setFileOwnership(ctx, id, &mount, ctrConfig)
-		if err != nil {
-			return err
-		}
-
 		if err := s.copyFileToContainer(ctx, id, content, mount); err != nil {
 			return err
 		}
@@ -129,30 +122,6 @@ func (s *composeService) setDefaultTarget(file *types.FileReferenceConfig, mount
 	}
 }
 
-func (s *composeService) setFileOwnership(ctx context.Context, id string, file *types.FileReferenceConfig, ctrConfig *container.Config) (*container.Config, error) {
-	if file.UID != "" || file.GID != "" {
-		return ctrConfig, nil
-	}
-
-	if ctrConfig == nil {
-		ctr, err := s.apiClient().ContainerInspect(ctx, id)
-		if err != nil {
-			return nil, err
-		}
-		ctrConfig = ctr.Config
-	}
-
-	parts := strings.Split(ctrConfig.User, ":")
-	if len(parts) > 0 {
-		file.UID = parts[0]
-	}
-	if len(parts) > 1 {
-		file.GID = parts[1]
-	}
-
-	return ctrConfig, nil
-}
-
 func (s *composeService) copyFileToContainer(ctx context.Context, id, content string, file types.FileReferenceConfig) error {
 	b, err := createTar(content, file)
 	if err != nil {
@@ -160,7 +129,7 @@ func (s *composeService) copyFileToContainer(ctx context.Context, id, content st
 	}
 
 	return s.apiClient().CopyToContainer(ctx, id, "/", &b, container.CopyToContainerOptions{
-		CopyUIDGID: true,
+		CopyUIDGID: file.UID != "" || file.GID != "",
 	})
 }
 
diff --git a/pkg/e2e/fixtures/env-secret/compose.yaml b/pkg/e2e/fixtures/env-secret/compose.yaml
index ef272419..51052d36 100644
--- a/pkg/e2e/fixtures/env-secret/compose.yaml
+++ b/pkg/e2e/fixtures/env-secret/compose.yaml
@@ -14,23 +14,6 @@ services:
         mode: 0440
     command: cat /run/secrets/bar
 
-  bar:
-    image: alpine
-    user: "1005"
-    secrets:
-      - source: secret
-        target: bar
-    command: cat /run/secrets/bar
-
-  zot:
-    image: alpine
-    user: "1005:1005"
-    secrets:
-      - source: secret
-        target: bar
-    command: cat /run/secrets/bar
-
-
 secrets:
   secret:
     environment: SECRET



Test patch:
diff --git a/pkg/e2e/secrets_test.go b/pkg/e2e/secrets_test.go
index dde21061..3e389511 100644
--- a/pkg/e2e/secrets_test.go
+++ b/pkg/e2e/secrets_test.go
@@ -17,7 +17,6 @@
 package e2e
 
 import (
-	"strings"
 	"testing"
 
 	"gotest.tools/v3/icmd"
@@ -41,28 +40,6 @@ func TestSecretFromEnv(t *testing.T) {
 			})
 		res.Assert(t, icmd.Expected{Out: "-r--r-----    1 1005     1005"})
 	})
-	t.Run("secret uid from user", func(t *testing.T) {
-		res := c.RunDockerCmd(t, "version", "--format", "{{ .Server.Version }}")
-		if strings.HasPrefix(res.Stdout(), "27.") {
-			t.Skip("USER uid:gid is not supported")
-		}
-		res = icmd.RunCmd(c.NewDockerComposeCmd(t, "-f", "./fixtures/env-secret/compose.yaml", "run", "bar", "ls", "-al", "/var/run/secrets/bar"),
-			func(cmd *icmd.Cmd) {
-				cmd.Env = append(cmd.Env, "SECRET=BAR")
-			})
-		res.Assert(t, icmd.Expected{Out: "-r--r--r--    1 1005     root"})
-	})
-	t.Run("secret uid:gid from user", func(t *testing.T) {
-		res := c.RunDockerCmd(t, "version", "--format", "{{ .Server.Version }}")
-		if strings.HasPrefix(res.Stdout(), "27.") {
-			t.Skip("USER uid:gid is not supported")
-		}
-		res = icmd.RunCmd(c.NewDockerComposeCmd(t, "-f", "./fixtures/env-secret/compose.yaml", "run", "zot", "ls", "-al", "/var/run/secrets/bar"),
-			func(cmd *icmd.Cmd) {
-				cmd.Env = append(cmd.Env, "SECRET=BAR")
-			})
-		res.Assert(t, icmd.Expected{Out: "-r--r--r--    1 1005     1005"})
-	})
 }
 
 func TestSecretFromInclude(t *testing.T) {