Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit a9364e3

Browse files
authored
Copy Docker config into controller container (#89)
* Mount Docker config into controller container * Check if docker config exists and its a regular file * Copy docker config file * poll on inspectResp.Running to wait for the command to exit
1 parent f4d48a2 commit a9364e3

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

pkg/standalone/containers.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package standalone
22

33
import (
4+
"archive/tar"
5+
"bytes"
46
"context"
57
"errors"
68
"fmt"
@@ -27,6 +29,100 @@ const controllerContainerName = "docker-model-runner"
2729
// the conflicting container in a capture group.
2830
var concurrentInstallMatcher = regexp.MustCompile(`is already in use by container "([a-z0-9]+)"`)
2931

32+
// copyDockerConfigToContainer copies the Docker config file from the host to the container
33+
// and sets up proper ownership and permissions for the modelrunner user.
34+
func copyDockerConfigToContainer(ctx context.Context, dockerClient *client.Client, containerID string) error {
35+
dockerConfigPath := os.ExpandEnv("$HOME/.docker/config.json")
36+
if s, err := os.Stat(dockerConfigPath); err != nil || s.Mode()&os.ModeType != 0 {
37+
return nil
38+
}
39+
40+
configData, err := os.ReadFile(dockerConfigPath)
41+
if err != nil {
42+
return fmt.Errorf("failed to read Docker config file: %w", err)
43+
}
44+
45+
var buf bytes.Buffer
46+
tw := tar.NewWriter(&buf)
47+
header := &tar.Header{
48+
Name: ".docker/config.json",
49+
Mode: 0600,
50+
Size: int64(len(configData)),
51+
}
52+
if err := tw.WriteHeader(header); err != nil {
53+
return fmt.Errorf("failed to write tar header: %w", err)
54+
}
55+
if _, err := tw.Write(configData); err != nil {
56+
return fmt.Errorf("failed to write config data to tar: %w", err)
57+
}
58+
if err := tw.Close(); err != nil {
59+
return fmt.Errorf("failed to close tar writer: %w", err)
60+
}
61+
62+
// Ensure the .docker directory exists
63+
mkdirCmd := "mkdir -p /home/modelrunner/.docker && chown modelrunner:modelrunner /home/modelrunner/.docker"
64+
if err := execInContainer(ctx, dockerClient, containerID, mkdirCmd); err != nil {
65+
return err
66+
}
67+
68+
// Copy directly into the .docker directory
69+
err = dockerClient.CopyToContainer(ctx, containerID, "/home/modelrunner", &buf, container.CopyToContainerOptions{
70+
CopyUIDGID: true,
71+
})
72+
if err != nil {
73+
return fmt.Errorf("failed to copy config file to container: %w", err)
74+
}
75+
76+
// Set correct ownership and permissions
77+
chmodCmd := "chown modelrunner:modelrunner /home/modelrunner/.docker/config.json && chmod 600 /home/modelrunner/.docker/config.json"
78+
if err := execInContainer(ctx, dockerClient, containerID, chmodCmd); err != nil {
79+
return err
80+
}
81+
82+
return nil
83+
}
84+
85+
func execInContainer(ctx context.Context, dockerClient *client.Client, containerID, cmd string) error {
86+
execConfig := container.ExecOptions{
87+
Cmd: []string{"sh", "-c", cmd},
88+
}
89+
execResp, err := dockerClient.ContainerExecCreate(ctx, containerID, execConfig)
90+
if err != nil {
91+
return fmt.Errorf("failed to create exec for command '%s': %w", cmd, err)
92+
}
93+
if err := dockerClient.ContainerExecStart(ctx, execResp.ID, container.ExecStartOptions{}); err != nil {
94+
return fmt.Errorf("failed to start exec for command '%s': %w", cmd, err)
95+
}
96+
97+
// Create a timeout context for the polling loop
98+
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
99+
defer cancel()
100+
101+
// Poll until the command finishes or timeout occurs
102+
for {
103+
inspectResp, err := dockerClient.ContainerExecInspect(ctx, execResp.ID)
104+
if err != nil {
105+
return fmt.Errorf("failed to inspect exec for command '%s': %w", cmd, err)
106+
}
107+
108+
if !inspectResp.Running {
109+
// Command has finished, now we can safely check the exit code
110+
if inspectResp.ExitCode != 0 {
111+
return fmt.Errorf("command '%s' failed with exit code %d", cmd, inspectResp.ExitCode)
112+
}
113+
return nil
114+
}
115+
116+
// Brief sleep to avoid busy polling, with timeout check
117+
select {
118+
case <-time.After(100 * time.Millisecond):
119+
// Continue polling
120+
case <-timeoutCtx.Done():
121+
return fmt.Errorf("command '%s' timed out after 10 seconds", cmd)
122+
}
123+
}
124+
}
125+
30126
// FindControllerContainer searches for a running controller container. It
31127
// returns the ID of the container (if found), the container name (if any), the
32128
// full container summary (if found), or any error that occurred.
@@ -196,6 +292,12 @@ func CreateControllerContainer(ctx context.Context, dockerClient *client.Client,
196292
_ = dockerClient.ContainerRemove(ctx, resp.ID, container.RemoveOptions{Force: true})
197293
return fmt.Errorf("failed to start container %s: %w", controllerContainerName, err)
198294
}
295+
296+
// Copy Docker config file if it exists
297+
if err := copyDockerConfigToContainer(ctx, dockerClient, resp.ID); err != nil {
298+
// Log warning but continue - don't fail container creation
299+
printer.Printf("Warning: failed to copy Docker config: %v\n", err)
300+
}
199301
return nil
200302
}
201303

0 commit comments

Comments
 (0)