Fix environ substitutions in `docker commit --change ...`

The building machinery was being handed an uninitialized container
Config.  This changes it to use the target container's Config.

Resolves #30538

Signed-off-by: Anthony Sottile <asottile@umich.edu>
diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go
index dabab3b..fd95420 100644
--- a/api/server/router/image/image_routes.go
+++ b/api/server/router/image/image_routes.go
@@ -13,7 +13,6 @@
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
-	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/pkg/ioutils"
@@ -46,9 +45,6 @@
 	if err != nil && err != io.EOF { //Do not fail if body is empty.
 		return err
 	}
-	if c == nil {
-		c = &container.Config{}
-	}
 
 	commitCfg := &backend.ContainerCommitConfig{
 		ContainerCommitConfig: types.ContainerCommitConfig{
diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go
index b62d6fc..20f1650 100644
--- a/builder/dockerfile/builder.go
+++ b/builder/dockerfile/builder.go
@@ -396,7 +396,8 @@
 	}
 
 	dispatchRequest := newDispatchRequest(b, dockerfile.EscapeToken, nil, newBuildArgs(b.options.BuildArgs), newStagesBuildResults())
-	dispatchRequest.state.runConfig = config
+	// We make mutations to the configuration, ensure we have a copy
+	dispatchRequest.state.runConfig = copyRunConfig(config)
 	dispatchRequest.state.imageID = config.Image
 	for _, cmd := range commands {
 		err := dispatch(dispatchRequest, cmd)
diff --git a/daemon/commit.go b/daemon/commit.go
index 0053132..1bdbd6b 100644
--- a/daemon/commit.go
+++ b/daemon/commit.go
@@ -149,6 +149,10 @@
 		defer daemon.containerUnpause(container)
 	}
 
+	if c.MergeConfigs && c.Config == nil {
+		c.Config = container.Config
+	}
+
 	newConfig, err := dockerfile.BuildFromConfig(c.Config, c.Changes)
 	if err != nil {
 		return "", err
diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go
index 58a50ce..057c2d6 100644
--- a/integration-cli/docker_cli_commit_test.go
+++ b/integration-cli/docker_cli_commit_test.go
@@ -121,11 +121,19 @@
 		"test", "test-commit")
 	imageID = strings.TrimSpace(imageID)
 
+	// The ordering here is due to `PATH` being overridden from the container's
+	// ENV.  On windows, the container doesn't have a `PATH` ENV variable so
+	// the ordering is the same as the cli.
+	expectedEnv := "[PATH=/foo DEBUG=true test=1]"
+	if testEnv.DaemonPlatform() == "windows" {
+		expectedEnv = "[DEBUG=true test=1 PATH=/foo]"
+	}
+
 	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
 	prefix = strings.ToUpper(prefix) // Force C: as that's how WORKDIR is normalized on Windows
 	expected := map[string]string{
 		"Config.ExposedPorts": "map[8080/tcp:{}]",
-		"Config.Env":          "[DEBUG=true test=1 PATH=/foo]",
+		"Config.Env":          expectedEnv,
 		"Config.Labels":       "map[foo:bar]",
 		"Config.Cmd":          "[/bin/sh]",
 		"Config.WorkingDir":   prefix + slash + "opt",
diff --git a/integration/image/commit_test.go b/integration/image/commit_test.go
new file mode 100644
index 0000000..13edbe1
--- /dev/null
+++ b/integration/image/commit_test.go
@@ -0,0 +1,47 @@
+package image
+
+import (
+	"context"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/integration/util/request"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCommitInheritsEnv(t *testing.T) {
+	defer setupTest(t)()
+	client := request.NewAPIClient(t)
+	ctx := context.Background()
+
+	createResp1, err := client.ContainerCreate(ctx, &container.Config{Image: "busybox"}, nil, nil, "")
+	require.NoError(t, err)
+
+	commitResp1, err := client.ContainerCommit(ctx, createResp1.ID, types.ContainerCommitOptions{
+		Changes:   []string{"ENV PATH=/bin"},
+		Reference: "test-commit-image",
+	})
+	require.NoError(t, err)
+
+	image1, _, err := client.ImageInspectWithRaw(ctx, commitResp1.ID)
+	require.NoError(t, err)
+
+	expectedEnv1 := []string{"PATH=/bin"}
+	assert.Equal(t, expectedEnv1, image1.Config.Env)
+
+	createResp2, err := client.ContainerCreate(ctx, &container.Config{Image: image1.ID}, nil, nil, "")
+	require.NoError(t, err)
+
+	commitResp2, err := client.ContainerCommit(ctx, createResp2.ID, types.ContainerCommitOptions{
+		Changes:   []string{"ENV PATH=/usr/bin:$PATH"},
+		Reference: "test-commit-image",
+	})
+	require.NoError(t, err)
+
+	image2, _, err := client.ImageInspectWithRaw(ctx, commitResp2.ID)
+	require.NoError(t, err)
+	expectedEnv2 := []string{"PATH=/usr/bin:/bin"}
+	assert.Equal(t, expectedEnv2, image2.Config.Env)
+}