Repository: docker/cli
Pull request #5858: run: flag to include the docker socket
Author: login: stevvooe
Labels: name: impact/changelog
name: kind/experimental
name: kind/feature
name: status/2-code-review
name: area/authentication
Created: 2025-02-21T22:47:48Z | Merged: 2025-04-16T08:49:27Z

Adds a flag, `--use-api-socket` that can be used to start a container with the correctly configured parameters to ensure that accessing the docker socket will work with out the fiddly flags.

There are a few problems with this approach:
1. We need a reliably way to clean up the configuration file. This currently is put into a tmp file then bind mounted. There is probably a better way to do this such as copying in the file.
2. We need a way to resolve the correct socket outside the container. If a different socket is used or a address and port, this will attempt to bind mount a nonexistent socket.

Either way, this is good start and resolves a long standing issue.

To try it out, you can do the following:

```
❯ ./docker run --rm -it --use-api-socket docker:cli
/ # docker pull redis
Using default tag: latest
latest: Pulling from library/redis
Digest: sha256:93a8d83b707d0d6a1b9186edecca2e37f83722ae0e398aee4eea0ff17c2fad0e
Status: Image is up to date for redis:latest
docker.io/library/redis:latest
```

This PR is somewhat incomplete and needs the following:
- [x] Address credentials leak
- [x] Address socket address location
- [ ] Implement unit and e2e tests
- [x] Update man pages and docs

```markdown changelog
- Experimental: add a new `--use-api-socket` flag on `docker run` and `docker create` to enable access to Docker socket from inside a container and to share credentials from the host with the container.
```

![image](https://github.com/user-attachments/assets/e08df601-4eb9-4877-936e-2fac12840c04)




Diff:
diff --git a/cli/command/container/create.go b/cli/command/container/create.go
index b23f1963be8e..bba18880aa4a 100644
--- a/cli/command/container/create.go
+++ b/cli/command/container/create.go
@@ -1,11 +1,15 @@
 package container
 
 import (
+	"archive/tar"
+	"bytes"
 	"context"
 	"fmt"
 	"io"
 	"net/netip"
 	"os"
+	"path"
+	"strings"
 
 	"github.com/containerd/platforms"
 	"github.com/distribution/reference"
@@ -13,13 +17,17 @@ import (
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command/completion"
 	"github.com/docker/cli/cli/command/image"
+	"github.com/docker/cli/cli/config/configfile"
+	"github.com/docker/cli/cli/config/types"
 	"github.com/docker/cli/cli/internal/jsonstream"
 	"github.com/docker/cli/cli/streams"
 	"github.com/docker/cli/cli/trust"
 	"github.com/docker/cli/opts"
 	"github.com/docker/docker/api/types/container"
 	imagetypes "github.com/docker/docker/api/types/image"
+	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/api/types/versions"
+	"github.com/docker/docker/client"
 	"github.com/docker/docker/errdefs"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
@@ -35,11 +43,12 @@ const (
 )
 
 type createOptions struct {
-	name      string
-	platform  string
-	untrusted bool
-	pull      string // always, missing, never
-	quiet     bool
+	name         string
+	platform     string
+	untrusted    bool
+	pull         string // always, missing, never
+	quiet        bool
+	useAPISocket bool
 }
 
 // NewCreateCommand creates a new cobra.Command for `docker create`
@@ -70,6 +79,8 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
 	flags.StringVar(&options.name, "name", "", "Assign a name to the container")
 	flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
 	flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
+	flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
+	flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Marks flag as experimental for now.
 
 	// Add an explicit help that doesn't have a `-h` to prevent the conflict
 	// with hostname
@@ -179,20 +190,20 @@ func (cid *cidFile) Write(id string) error {
 	return nil
 }
 
-func newCIDFile(path string) (*cidFile, error) {
-	if path == "" {
+func newCIDFile(cidPath string) (*cidFile, error) {
+	if cidPath == "" {
 		return &cidFile{}, nil
 	}
-	if _, err := os.Stat(path); err == nil {
-		return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", path)
+	if _, err := os.Stat(cidPath); err == nil {
+		return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", cidPath)
 	}
 
-	f, err := os.Create(path)
+	f, err := os.Create(cidPath)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to create the container ID file")
 	}
 
-	return &cidFile{path: path, file: f}, nil
+	return &cidFile{path: cidPath, file: f}, nil
 }
 
 //nolint:gocyclo
@@ -239,6 +250,73 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	var apiSocketCreds map[string]types.AuthConfig
+
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		var envvarPresent bool
+		for _, envvar := range containerCfg.Config.Env {
+			if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
+				envvarPresent = true
+			}
+		}
+
+		// If the DOCKER_CONFIG env var is already present, we assume the client knows
+		// what they're doing and don't inject the creds.
+		if !envvarPresent {
+			// Set our special little location for the config file.
+			containerCfg.Config.Env = append(containerCfg.Config.Env,
+				"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
+
+			// Resolve this here for later, ensuring we error our before we create the container.
+			creds, err := dockerCli.ConfigFile().GetAllCredentials()
+			if err != nil {
+				return "", fmt.Errorf("resolving credentials failed: %w", err)
+			}
+			apiSocketCreds = creds // inject these after container creation.
+		}
+	}
+
 	var platform *specs.Platform
 	// Engine API version 1.41 first introduced the option to specify platform on
 	// create. It will produce an error if you try to set a platform on older API
@@ -286,11 +364,25 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
+
+	if options.useAPISocket && apiSocketCreds != nil {
+		// Create a new config file with just the auth.
+		newConfig := &configfile.ConfigFile{
+			AuthConfigs: apiSocketCreds,
+		}
+
+		if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
+			return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
+		}
+	}
+
+	return containerID, err
 }
 
 // check the DNS settings passed via --dns against localhost regexp to warn if
@@ -321,3 +413,39 @@ func validatePullOpt(val string) error {
 		)
 	}
 }
+
+// copyDockerConfigIntoContainer takes the client configuration and copies it
+// into the container.
+//
+// The path should be an absolute path in the container, commonly
+// /root/.docker/config.json.
+func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
+	var configBuf bytes.Buffer
+	if err := config.SaveToWriter(&configBuf); err != nil {
+		return fmt.Errorf("saving creds: %w", err)
+	}
+
+	// We don't need to get super fancy with the tar creation.
+	var tarBuf bytes.Buffer
+	tarWriter := tar.NewWriter(&tarBuf)
+	tarWriter.WriteHeader(&tar.Header{
+		Name: configPath,
+		Size: int64(configBuf.Len()),
+		Mode: 0o600,
+	})
+
+	if _, err := io.Copy(tarWriter, &configBuf); err != nil {
+		return fmt.Errorf("writing config to tar file for config copy: %w", err)
+	}
+
+	if err := tarWriter.Close(); err != nil {
+		return fmt.Errorf("closing tar for config copy failed: %w", err)
+	}
+
+	if err := dockerAPI.CopyToContainer(ctx, containerID, "/",
+		&tarBuf, container.CopyToContainerOptions{}); err != nil {
+		return fmt.Errorf("copying config.json into container failed: %w", err)
+	}
+
+	return nil
+}
diff --git a/cli/command/container/run.go b/cli/command/container/run.go
index 702669e19b00..efbc457b23fa 100644
--- a/cli/command/container/run.go
+++ b/cli/command/container/run.go
@@ -60,6 +60,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
 	flags.StringVar(&options.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
 	flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before running ("`+PullImageAlways+`", "`+PullImageMissing+`", "`+PullImageNever+`")`)
 	flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
+	flags.BoolVarP(&options.createOptions.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
 
 	// Add an explicit help that doesn't have a `-h` to prevent the conflict
 	// with hostname
diff --git a/docs/reference/commandline/container_create.md b/docs/reference/commandline/container_create.md
index a2394eab28c2..3b812a77747f 100644
--- a/docs/reference/commandline/container_create.md
+++ b/docs/reference/commandline/container_create.md
@@ -104,6 +104,7 @@ Create a new container
 | `--tmpfs`                 | `list`        |           | Mount a tmpfs directory                                                                                                                                                                                                                                                                                          |
 | `-t`, `--tty`             | `bool`        |           | Allocate a pseudo-TTY                                                                                                                                                                                                                                                                                            |
 | `--ulimit`                | `ulimit`      |           | Ulimit options                                                                                                                                                                                                                                                                                                   |
+| `--use-api-socket`        | `bool`        |           | Bind mount Docker API socket and required auth                                                                                                                                                                                                                                                                   |
 | `-u`, `--user`            | `string`      |           | Username or UID (format: <name\|uid>[:<group\|gid>])                                                                                                                                                                                                                                                             |
 | `--userns`                | `string`      |           | User namespace to use                                                                                                                                                                                                                                                                                            |
 | `--uts`                   | `string`      |           | UTS namespace to use                                                                                                                                                                                                                                                                                             |
diff --git a/docs/reference/commandline/container_run.md b/docs/reference/commandline/container_run.md
index 4ac2ebee3a44..0e5c6e2ce2f3 100644
--- a/docs/reference/commandline/container_run.md
+++ b/docs/reference/commandline/container_run.md
@@ -107,6 +107,7 @@ Create and run a new container from an image
 | [`--tmpfs`](#tmpfs)                                   | `list`        |           | Mount a tmpfs directory                                                                                                                                                                                                                                                                                          |
 | [`-t`](#tty), [`--tty`](#tty)                         | `bool`        |           | Allocate a pseudo-TTY                                                                                                                                                                                                                                                                                            |
 | [`--ulimit`](#ulimit)                                 | `ulimit`      |           | Ulimit options                                                                                                                                                                                                                                                                                                   |
+| `--use-api-socket`                                    | `bool`        |           | Bind mount Docker API socket and required auth                                                                                                                                                                                                                                                                   |
 | `-u`, `--user`                                        | `string`      |           | Username or UID (format: <name\|uid>[:<group\|gid>])                                                                                                                                                                                                                                                             |
 | [`--userns`](#userns)                                 | `string`      |           | User namespace to use                                                                                                                                                                                                                                                                                            |
 | [`--uts`](#uts)                                       | `string`      |           | UTS namespace to use                                                                                                                                                                                                                                                                                             |
diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md
index 59488a5404af..a3068217095e 100644
--- a/docs/reference/commandline/create.md
+++ b/docs/reference/commandline/create.md
@@ -104,6 +104,7 @@ Create a new container
 | `--tmpfs`                 | `list`        |           | Mount a tmpfs directory                                                                                                                                                                                                                                                                                          |
 | `-t`, `--tty`             | `bool`        |           | Allocate a pseudo-TTY                                                                                                                                                                                                                                                                                            |
 | `--ulimit`                | `ulimit`      |           | Ulimit options                                                                                                                                                                                                                                                                                                   |
+| `--use-api-socket`        | `bool`        |           | Bind mount Docker API socket and required auth                                                                                                                                                                                                                                                                   |
 | `-u`, `--user`            | `string`      |           | Username or UID (format: <name\|uid>[:<group\|gid>])                                                                                                                                                                                                                                                             |
 | `--userns`                | `string`      |           | User namespace to use                                                                                                                                                                                                                                                                                            |
 | `--uts`                   | `string`      |           | UTS namespace to use                                                                                                                                                                                                                                                                                             |
diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md
index f9e0fbfcf26a..0e9a90ef3efd 100644
--- a/docs/reference/commandline/run.md
+++ b/docs/reference/commandline/run.md
@@ -107,6 +107,7 @@ Create and run a new container from an image
 | `--tmpfs`                 | `list`        |           | Mount a tmpfs directory                                                                                                                                                                                                                                                                                          |
 | `-t`, `--tty`             | `bool`        |           | Allocate a pseudo-TTY                                                                                                                                                                                                                                                                                            |
 | `--ulimit`                | `ulimit`      |           | Ulimit options                                                                                                                                                                                                                                                                                                   |
+| `--use-api-socket`        | `bool`        |           | Bind mount Docker API socket and required auth                                                                                                                                                                                                                                                                   |
 | `-u`, `--user`            | `string`      |           | Username or UID (format: <name\|uid>[:<group\|gid>])                                                                                                                                                                                                                                                             |
 | `--userns`                | `string`      |           | User namespace to use                                                                                                                                                                                                                                                                                            |
 | `--uts`                   | `string`      |           | UTS namespace to use                                                                                                                                                                                                                                                                                             |


Reviews:
user: login: Benehiko
body: To me everything except the credentials part looks alright. I know we have already discussed on the PR a bit about it. If this is critical to get in now then I would be okay with merging as is, but we would end up having credential sync issues:

- On credential expiration
- Re-authenticating on the Host would completely invalidate any credentials in the container
- Currently CLI doesn't really use the OAuth Access and Refresh tokens - except for fetching a PAT. If this were to change, token refreshes would also invalidate container credentials. 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T17:49:38Z
user: login: slimslenderslacks
body: @stevvooe the other thing about the run command line is that this won't immediately be available to use in compose without it also supporting this option. Currently, the container that needs this on the MCP gateway side is being started from a compose.yaml file inside of a Desktop extension. So I'm assuming that Desktop extensions just use compose to launch this container.  Does that make sense?
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-14T22:59:45Z
user: login: Benehiko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T06:32:59Z
user: login: thaJeztah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T12:58:20Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:25:24Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:26:10Z
user: login: thaJeztah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T13:34:49Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:38:42Z
user: login: thaJeztah
body: Overall this looks ok; left two comments; let me know if you want to look at those as part of this PR or separate
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T17:39:02Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:55:29Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:55:56Z
user: login: thaJeztah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T20:32:53Z
user: login: thaJeztah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T20:38:07Z
user: login: thaJeztah
body: LGTM

as discussed, we're gonna bring this one in as a starting point; much more things to fix around this, which is why we added the "experimental" annotation (as the implementation will likely change in future).
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-15T20:40:00Z
user: login: stevvooe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:15:15Z
user: login: thaJeztah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T21:47:17Z
user: login: Benehiko
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T04:59:20Z

Review comments:
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
+	}
path: cli/command/container/create.go
user: login: Benehiko
body: I think we should rather use the `DOCKER_AUTH_CONFIG` here and push the auth configs into the environment variable instead (as discussed with @thaJeztah).

It's already a commonly used variable among TestContainers and GitLab. They advise to use it and then write the contents of the environment variable to a file (`~/.docker/config.json`). 
See:
- https://docs.gitlab.com/ci/docker/authenticate_registry/#option-3-use-docker_auth_config
- https://github.com/testcontainers/testcontainers-go/blob/6d13d7a72bf5d1f15002e8c0b4d88ea8b88138f3/docker_auth.go#L262-L282

I have a PR open which would use this natively here https://github.com/docker/cli/pull/6008
created_at: 2025-04-15T06:32:56Z
author_association: MEMBER
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
path: cli/command/container/create.go
user: login: thaJeztah
body: We need to find a solution for this part; the `DOCKER_CONFIG` option not only controls the location of where the CLI looks for the config-file, but (currently) all state of the CLI, including contexts, cli-plugins, and state stored by various plugins, such as `buildx` and `compose` 😞. Setting a custom path would likely result in things breaking in containers that have some config and/or state set up.
created_at: 2025-04-15T12:58:20Z
author_association: MEMBER
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
+	}
path: cli/command/container/create.go
user: login: stevvooe
body: Do any existing docker clis support this env var? This seems like a nice addition but can be done later.
created_at: 2025-04-15T13:25:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
path: cli/command/container/create.go
user: login: stevvooe
body: Can you demonstrate a container that breaks today? With the `docker:cli` image, there is no existing config in place and it seems like we could easily work around this, if needed.
created_at: 2025-04-15T13:26:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
path: cli/command/container/create.go
user: login: thaJeztah
body: At least the `docker:cli` plugin will now use `/run/secrets/docker/` for all its state, e.g. things like `buildx` will now save build-history there; https://github.com/docker/buildx/blob/eb74b483bd991c9afb5e0661d3c182266bf83153/util/confutil/config.go#L62-L67

I don't know about other containers and what they have configured.
created_at: 2025-04-15T13:34:49Z
author_association: MEMBER
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
path: cli/command/container/create.go
user: login: stevvooe
body: `docker:cli` is a container image with the cli inside. 

I'm not sure I see the problem here. Do we want to maintain the existing path in the home directory then?
created_at: 2025-04-15T13:38:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
+
+	if options.useAPISocket {
+		creds, err := dockerCli.ConfigFile().GetAllCredentials()
+		if err != nil {
+			return "", fmt.Errorf("resolving credentials failed: %w", err)
+		}
+
+		// Create a new config file with just the auth.
+		newConfig := &configfile.ConfigFile{
+			AuthConfigs: creds,
+		}
+
+		if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
+			return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
+		}
+	}
path: cli/command/container/create.go
user: login: thaJeztah
body: Not a blocker, but perhaps something we should look at after this; both these errors are returned _after_ the container was created, so potentially the user would end up with a container that was created, but the CLI won't return its ID.

I realize this combination of "create container" and "provision it with configs" is never gonna be an atomic, operation, but we _could_ possibly put the `dockerCli.ConfigFile().GetAllCredentials()` early in this function, so that we don't attempt to create a container if we're gonna fail (because we can't read the credentials).

The "copy credentials to the container" of course requires the container to be present, so that must be kept at the end; we _could_ consider;

- still returning the `containerID` (to provide a reference to what's created
- (TBD); print the failure as a warning and proceed (but this may not be ideal if the `create` was part of a `docker run`, as we'd be mixing our warnings with container output).
created_at: 2025-04-15T17:29:56Z
author_association: MEMBER
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
path: cli/command/container/create.go
user: login: thaJeztah
body: Slightly related; looks like we may be discarding this error if failing to write to a container-id file failed, and something fails below.
created_at: 2025-04-15T17:33:23Z
author_association: MEMBER
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
+
+	if options.useAPISocket {
+		creds, err := dockerCli.ConfigFile().GetAllCredentials()
+		if err != nil {
+			return "", fmt.Errorf("resolving credentials failed: %w", err)
+		}
+
+		// Create a new config file with just the auth.
+		newConfig := &configfile.ConfigFile{
+			AuthConfigs: creds,
+		}
+
+		if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
+			return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
+		}
+	}
path: cli/command/container/create.go
user: login: stevvooe
body: Pushed up cred resolution but its unclear where this cid file is being written.
created_at: 2025-04-15T17:55:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
path: cli/command/container/create.go
user: login: stevvooe
body: We return it below but first try to resolve and write the creds.
created_at: 2025-04-15T17:55:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
path: cli/command/container/create.go
user: login: thaJeztah
body: Yeah, it would likely be a corner case; I was looking at these lines, where we don't return the containerID, and don't include the error about the container id-file being written.


```bash
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
	return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
}
```

We could do an `errors.Join` or something along those lines, but not sure if that's super great, so let's keep that for follow-ups.

Doing the "copy to container" as part of `docker start` was something that crossed my mind, but not sure if we want to / can update the files before start (that may open up possible race-conditions as well).
created_at: 2025-04-15T20:32:53Z
author_association: MEMBER
diff_hunk: @@ -239,6 +248,53 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 		return nil
 	}
 
+	const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
+	if options.useAPISocket {
+		// We'll create two new mounts to handle this flag:
+		// 1. Mount the actual docker socket.
+		// 2. A synthezised ~/.docker/config.json with resolved tokens.
+
+		socket := dockerCli.DockerEndpoint().Host
+		if !strings.HasPrefix(socket, "unix://") {
+			return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
+		}
+		socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
+
+		containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      socket,
+			Target:      "/var/run/docker.sock",
+			BindOptions: &mount.BindOptions{},
+		})
+
+		/*
+
+		   Ideally, we'd like to copy the config into a tmpfs but unfortunately,
+		   the mounts won't be in place until we start the container. This can
+		   leave around the config if the container doesn't get deleted.
+
+		   We are using the most compose-secret-compatible approach,
+		   which is implemented at
+		   https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
+
+		   // Prepare a tmpfs mount for our credentials so they go away after the
+		   // container exits. We'll copy into this mount after the container is
+		   // created.
+		   containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
+		       Type:   mount.TypeTmpfs,
+		       Target: "/docker/",
+		       TmpfsOptions: &mount.TmpfsOptions{
+		           SizeBytes: 1 << 20, // only need a small partition
+		           Mode:      0o600,
+		       },
+		   })
+		*/
+
+		// Set our special little location for the config file.
+		containerCfg.Config.Env = append(containerCfg.Config.Env,
+			"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
path: cli/command/container/create.go
user: login: thaJeztah
body: > I'm not sure I see the problem here. Do we want to maintain the existing path in the home directory then?

It's part of a general problem we already have; `~/.docker/` has grown to  become a bit of a kitchen-sink of things, and no longer is just the CLI's config, but various plugins started to use it for `<who knows what>`;  Docker Desktop stores various bits there, including sockets, and at some point, `docker scout` used it as cache, which ended up with multiple GB's of data that should've been temporary (I think that was fixed, and it now uses somewhere in `TEMP`, but it still stores `SBOMS` and other bits there).


After running a couple of builds from within a `docker:cli` container with this PR;

```
C /run
A /run/docker.sock
A /run/secrets
A /run/secrets/docker
A /run/secrets/docker/.token_seed
A /run/secrets/docker/.token_seed.lock
A /run/secrets/docker/buildx
A /run/secrets/docker/buildx/.buildNodeID
A /run/secrets/docker/buildx/.lock
A /run/secrets/docker/buildx/activity
A /run/secrets/docker/buildx/activity/default
A /run/secrets/docker/buildx/current
A /run/secrets/docker/buildx/defaults
A /run/secrets/docker/buildx/instances
A /run/secrets/docker/buildx/refs
A /run/secrets/docker/buildx/refs/default
A /run/secrets/docker/buildx/refs/default/default
A /run/secrets/docker/buildx/refs/default/default/lqga45irm59xvgavd4tntqcft
A /run/secrets/docker/buildx/refs/default/default/ovfmov0i5o63umkyhrhbmhpyu
A /run/secrets/docker/buildx/refs/default/default/w3m8ggkzghh1ivuunw1fl8ct3
A /run/secrets/docker/buildx/refs/default/default/1xf73waihbtkr53ijk716v8zp
A /run/secrets/docker/buildx/refs/default/default/6wmh6xclpvdzsrvkwgn4gdqp5
A /run/secrets/docker/buildx/refs/default/default/l15tkm6r6kwpntvvcrqgwhkfv
A /run/secrets/docker/config.json
```
created_at: 2025-04-15T20:38:07Z
author_association: MEMBER
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
path: cli/command/container/create.go
user: login: stevvooe
body: Do you know why we write the containerID to a file on create? That's a bit unexpected. I tried to keep a light foot through here.
created_at: 2025-04-15T21:15:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -286,11 +343,30 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
 	if warn := localhostDNSWarning(*hostConfig); warn != "" {
 		response.Warnings = append(response.Warnings, warn)
 	}
+
+	containerID = response.ID
 	for _, w := range response.Warnings {
 		_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
 	}
-	err = containerIDFile.Write(response.ID)
-	return response.ID, err
+	err = containerIDFile.Write(containerID)
path: cli/command/container/create.go
user: login: thaJeztah
body: Oh! It's not written to a file by default (that function's signature is a bit misleading); it's only written to a file if the uses passes the `--cidfile=<some path>` option.

If the flag isn't set, nothing is written and the function always returns nil

I'm not a huge fan of that feature, but I think it was added for some users that wanted to automate things and/or start containers through some init system (not a good fit)

- Also see; https://github.com/docker/cli/issues/5954
created_at: 2025-04-15T21:47:17Z
author_association: MEMBER

PR comments:
user: login: codecov-commenter
created_at: 2025-02-21T22:49:23Z
body: ## [Codecov](https://app.codecov.io/gh/docker/cli/pull/5858?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) Report
Attention: Patch coverage is `15.09434%` with `90 lines` in your changes missing coverage. Please review.
> Project coverage is 58.88%. Comparing base [(`1adc158`)](https://app.codecov.io/gh/docker/cli/commit/1adc1583a7c1e0d83164105b587a15d3e8965723?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) to head [(`1a502e9`)](https://app.codecov.io/gh/docker/cli/commit/1a502e91c937d340ecd3a62de09ae096290160d9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5858      +/-   ##
==========================================
- Coverage   59.05%   58.88%   -0.17%     
==========================================
  Files         357      357              
  Lines       29843    29941      +98     
==========================================
+ Hits        17624    17632       +8     
- Misses      11241    11329      +88     
- Partials      978      980       +2     
```

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: stevvooe
created_at: 2025-02-21T23:17:01Z
body: Alright, I just validated an approach to the credential leak issue using copy into the container. Let's validate the cli ux and then we can add that implementation if we want to go forward with the change.
author_association: CONTRIBUTOR
user: login: thaJeztah
created_at: 2025-02-24T11:38:40Z
body: This is a tricky one; bind-mounting the docker socket by setting these options from the CLI will break in many scenarios. The current approach in this PR uses the default location (`/var/run/docker.sock`), which would work with a default config for a Linux daemon, but won't work for a Windows daemon (which currently uses a named pipe as default). A similar case would happen if the daemon is local, but running rootless, in which case the socket would be in `XDG_RUNTIME_DIR` (if set). In a non-default setting, the daemon can be configured to use a socket on a different location (or no socket at all if the daemon is configured to only list on TCP). The tricky bit here is that when connecting with a remote daemon, the CLI won't have the information where the socket is located.

For a daemon running _locally_ (same machine), the CLI could have slightly more information, and look at the docker host configuration in the `docker context` used;

```bash
docker context inspect | jq .[].Endpoints.docker.Host
"unix:///var/run/docker.sock"
```

In general, these could be somewhat tricky even with the good location, as there's a potential race condition where the container is started before the API server (and thus socket) is up, in which case using the `-v` shorthand (which implicitly has "auto-create host path" enabled) can result in `/var/run/docker.sock` being created as a _directory_ if the path is missing (which could prevent the daemon from finishing startup).

I recall some older proposals to have an option on the daemon side to grant a container access to the API; for those, the daemon would handle setting up a socket and mounting it into the container. Those were a really long time ago, and IIRC the initial implementation was to do so automatically for any container with `--privileged`, but there was discussion to have a separate option for this (because forwarding the socket into a `--privileged` container could interfere with DIND cases, which would have their own socket _inside_ the container from the daemon running in it);

- https://github.com/moby/moby/pull/25347
- https://github.com/moby/moby/pull/9135
- https://github.com/moby/moby/issues/33784

I think something along those lines (i.e., having an option to forward the socket, and have the daemon set this up) could solve most of the issues mentioned earlier; the daemon would be able to set up the socket for the container, regardless how it was configured, which could even mean a dedicated socket for the container (which could potentially be expanded to more granular / limited access to the socket, instead of access to all endpoints).

Slightly related (forwarding a socket from the client to the daemon to allow ssh-agent-like constructs for forwarding into the container); this could possibly be tying in to the "forward authentication" part;

- https://github.com/moby/moby/pull/19667

author_association: MEMBER
user: login: thaJeztah
created_at: 2025-02-24T11:45:52Z
body: For the credentials; _potentially_ we could use some env-var, although env-vars may not be ideal as they will leak both into the container's config (`docker container inspect`), and any process inside the container. Forwarding "all credentials" is what's currently used by the classic builder, which encodes them in a `X-Registry-Config` header that contains all credentials for all registries the CLI is aware of; https://github.com/moby/moby/blob/0de01f700d9040f70d71d16fff2ea4aafc4af36b/api/server/router/build/build_routes.go#L335-L348

Possibly we can make the CLI aware of an env-var along those lines; IIRC, BuildKit requests auth from the CLI when building, so not 100% sure if that would work in that combination, and likely all CLI plugins would have to be updated to be aware of this approach.
author_association: MEMBER
user: login: laurazard
created_at: 2025-02-24T17:36:23Z
body: Tangential, but regarding:
> ... reliably way to clean up the configuration file. This currently is put into a tmp file then bind mounted. There is probably a better way to do this such as copying in the file.

Ultimately, this should be unnecessary and we should make the necessary changes to stop the "credentials in `~/.docker/config.json`" case so common. i.e., stop storing credentials together with CLI configurations (in a non-breaking way, and keep accepting those), either by just storing them elsewhere or using a "plaintext credential-helper", which would also solve a number of other issues.
author_association: MEMBER
user: login: stevvooe
created_at: 2025-02-24T18:36:50Z
body: @laurazard Yes but unfortunately, there is no way to call the credential helps from inside the container. We could have an ssh-agent like call api for it but that would require an interactive client to that to work correctly.

@thaJeztah It sounds like I should at least use a mount rather that volume notation to avoid the accidental file creation.
author_association: CONTRIBUTOR
user: login: laurazard
created_at: 2025-02-24T23:35:59Z
body: Right, that's definitely true. I think there's two separate issues that folks frequently run into that we should keep in mind:
- wanting to easily expose the docker socket inside of a container, to use, without sharing all credentials
	- this is annoying because other necessary configurations live in the same `config.json` file
- wanting to share some/all credentials for use inside a container
	- this commonly leads users to use the plaintext store instead of a credential helper, since these are easier to share with a container

It'd be awesome if whatever solution we came up with could account for/solve both cases (and splitting out creds – even if just to another file – from `config.json` would already help).
author_association: MEMBER
user: login: stevvooe
created_at: 2025-02-25T01:32:26Z
body: @laurazard Looking at compose, they actually use the same technique that I've employed here: https://github.com/docker/compose/blob/145bb8466dffd18d7828ca0bc0725b1059506098/pkg/compose/secrets.go#L31. The secrets are copied over to `/run/secrets`. It looks like that is a tmpfs _and_ the copy is done after start. This PR is a little problematic in that they are copied into the container's read-write layer. My notes on that are in https://github.com/docker/cli/pull/5858/files#diff-50fe347eb4bdafebc190aa42b2caa34b089a538cd444b8a151a763372e93c0cfR269. Basically, I'll have to start the container to employ this correctly, so we won't have it on create, which is probably ok.

Here's my proposal:
- [x] I'll move the this over to the `/run/secrets` tmpfs mount to ensure the secrets don't leak to the overlay filesystem. The copy will be done post container start but before we hand terminal the success off to the user.
- [x] I'll add some logic to better resolve the socket location and move to mounts to better control how it appears in the container.
- [ ] We can mark this flag as experimental. (no clue to do this, direction needed)
- [ ] I'll rally the troops a bit here to see if we can get a session-callback proposal in place or at least first-class support for a secret mounts, similar to what's happening buildkit.

I know this isn't ideal but this has been a long-lived pain point. The abstraction of the `--use-docker-socket` flag is high-level enough to where we can improve the credential access over time without breaking users, especially if we maintain it as an experimental feature.

I hope this is reasonable and can allow us to move forward.
author_association: CONTRIBUTOR
user: login: stevvooe
created_at: 2025-03-19T22:10:21Z
body: A few fixes that have been put into place:
1. The secrets are now in /run/secrets to follow the compose convention. We should probably use tmpfs there but compose doesn't do this today so we can leave it be. Ideally, we'd have /run be tmpfs and just have that mount at create time so we can have the convention of dumping secrects or whatever in there.
2. The socket address now resolves from the context.
I'll be updating tests once this CI runs and fails.

I'll need guidance on marking the flag as experimental.
author_association: CONTRIBUTOR
user: login: Benehiko
created_at: 2025-03-21T15:22:08Z
body: I've created a POC to instead share credentials over a unix socket which we could bind mount into a container. I don't know if that's the correct approach or if it's something that would be _better_ than what is inside this PR. https://github.com/docker/cli/pull/5948
author_association: MEMBER
user: login: stevvooe
created_at: 2025-03-25T15:37:49Z
body: @Benehiko We do have discussions around the best way to allow secrets access from within a container but we don't want to block this addition too much longer. We can always change the implementation to use such a system in the future.

Let's keep the discussion on your PR going and work from there.
author_association: CONTRIBUTOR
user: login: cpuguy83
created_at: 2025-03-25T16:44:39Z
body: Seems like this should be a feature on the engine rather than client side.
Maybe something like `--mount type=api-socket`.
Not sure how well this would work on the Windows side, though.
author_association: COLLABORATOR
user: login: stevvooe
created_at: 2025-03-25T16:47:09Z
body: > Seems like this should be a feature on the engine rather than client side.
For some implementations, client participation is required.

I like the idea of a mount long term but we'd have to teach every proxy about it. Plumbing for /var/run/docker.sock mostly already exists.
author_association: CONTRIBUTOR
user: login: stevvooe
created_at: 2025-03-25T16:59:11Z
body: @cpuguy83 Do you want me to change the flag to the more general `--use-api-socket`?
author_association: CONTRIBUTOR
user: login: cpuguy83
created_at: 2025-03-25T17:10:32Z
body: Either way for the flag is fine for me.
author_association: COLLABORATOR
user: login: stevvooe
created_at: 2025-04-02T02:30:36Z
body: @Benehiko Indeed, those are the current limitations we have here. I hope not to leave it in this condition. Thanks for your review!

I'll added some tests before the end of this week.
author_association: CONTRIBUTOR
user: login: stevvooe
created_at: 2025-04-14T23:01:22Z
body: @slimslenderslacks Yes, that would be a current limitation. We could port a similar implementation to compose. The secret machinery already uses the same technique as this PR so it should be straightforward.
author_association: CONTRIBUTOR
user: login: thaJeztah
created_at: 2025-04-15T14:29:00Z
body: For the "docker socket" part and setting `DOCKER_CONFIG`; I do not want to couple those efforts too tightly to this work in the short term; both have some problematic parts that should be tackled before we paint ourselves in a corner.

Some considerations;

- We can implement the CLI feature to natively support `DOCKER_AUTH_CONFIG`; this would allow `docker:cli` to use auth from that env-var directly
- If other tools need credentials, they can  `printenv DOCKER_AUTH_CONFIG > ~/.docker/config.json `
- We should also add an option to the CLI to dump the credentials in the format `DOCKER_AUTH_CONFIG` expects)
- :point_up: once `DOCKER_AUTH_CONFIG` is documented, we can expect other tools to adopt it though, so if those tools want to use auth without on-disk store, they can use that.
- :point_right: I should add that env-vars are not "great" in many ways (but used widely); this work should _not_ be the final state, and we continue to look at the (more general) auth work.

How to set the `DOCKER_AUTH_CONFIG`"? I'm considering that (short term) we can add a `--security-opt` for this that's handled by the CLI; e.g.;

- `--security-opt forward_auth=env` set the auth-confirmation as `DOCKER_AUTH_CONFIG` environment on the container
- (TBD); if we do want the option to write the config to a file instead it could be `--security-opt forward_auth=file` (using the logic from this PR)
- :point_up: this would provide "write a config file into the container" to be natively supported, without the user having to do so manually, and even if the CLI inside the container is either "old" or there is no `docker` CLI at all

author_association: MEMBER
user: login: stevvooe
created_at: 2025-04-15T14:55:40Z
body: @thaJeztah I think these are good suggestions for a follow up but not sure that the suggestions impact this PR. Let's get this merged and we can follow up with improvements.
author_association: CONTRIBUTOR
user: login: thaJeztah
created_at: 2025-04-15T15:14:55Z
body: > We can mark this flag as experimental. (no clue to do this, direction needed)

For the experimental annotation, you can use the same as for this flag (with `s/tree/use-api-socket/`); https://github.com/docker/cli/blob/1adc1583a7c1e0d83164105b587a15d3e8965723/cli/command/image/list.go#L66

Can you squash / cleanup commits, and it looks like `make -f docker.Makefile mddocs` is needed to regenerate / format the markdown docs.
author_association: MEMBER
user: login: thaJeztah
created_at: 2025-04-15T20:40:52Z
body: @Benehiko @cpuguy83 PTAL
author_association: MEMBER