Skip to content

Commit dc7bfae

Browse files
authored
Determine docker host via env then context (#2444)
* Determine docker host via env then context Previously always used the system default (eg /var/run/docker.sock) which would fail if the default docker socket was not installed, no longer symlinked to the home directory socket, or pointed to a daemon that wasn't running. This can happen when docker desktop and orbstack are running togehter on the same system. Now cog will try to find the daemon socket by looking at the following in order 1. the DOCKER_HOST env variable 2. the docker context specified by DOCKER_CONTEXT 3. the current docker context set in the docker config 4. the system default * remove unused code
1 parent 138ec81 commit dc7bfae

File tree

8 files changed

+111
-49
lines changed

8 files changed

+111
-49
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
1010
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3
1111
github.com/creack/pty v1.1.24
12-
github.com/docker/cli v28.1.1+incompatible
12+
github.com/docker/cli v28.3.0+incompatible
1313
github.com/docker/docker v28.1.1+incompatible
1414
github.com/docker/go-connections v0.5.0
1515
github.com/getkin/kin-openapi v0.128.0
@@ -126,6 +126,7 @@ require (
126126
github.com/felixge/httpsnoop v1.0.4 // indirect
127127
github.com/firefart/nonamedreturns v1.0.5 // indirect
128128
github.com/fsnotify/fsnotify v1.9.0 // indirect
129+
github.com/fvbommel/sortorder v1.1.0 // indirect
129130
github.com/fzipp/gocyclo v0.6.0 // indirect
130131
github.com/ghostiam/protogetter v0.3.9 // indirect
131132
github.com/go-critic/go-critic v0.12.0 // indirect
@@ -205,6 +206,7 @@ require (
205206
github.com/moby/go-archive v0.1.0 // indirect
206207
github.com/moby/locker v1.0.1 // indirect
207208
github.com/moby/patternmatcher v0.6.0 // indirect
209+
github.com/moby/sys/atomicwriter v0.1.0 // indirect
208210
github.com/moby/sys/sequential v0.6.0 // indirect
209211
github.com/moby/sys/signal v0.7.1 // indirect
210212
github.com/moby/sys/user v0.4.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=
177177
github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=
178178
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
179179
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
180+
github.com/docker/cli v28.3.0+incompatible h1:s+ttruVLhB5ayeuf2BciwDVxYdKi+RoUlxmwNHV3Vfo=
181+
github.com/docker/cli v28.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
180182
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
181183
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
182184
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
@@ -203,6 +205,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
203205
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
204206
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
205207
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
208+
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
209+
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
206210
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
207211
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
208212
github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4=

pkg/docker/api_client.go

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package docker
22

33
import (
4-
"bytes"
54
"context"
6-
"encoding/json"
75
"errors"
86
"fmt"
97
"io"
108
"net"
119
"os"
12-
"os/exec"
1310
"strconv"
1411
"strings"
1512

@@ -45,25 +42,26 @@ func NewAPIClient(ctx context.Context, opts ...Option) (*apiClient, error) {
4542
opt(clientOptions)
4643
}
4744

48-
// Check to see if we need to override docker host
49-
host := os.Getenv("DOCKER_HOST")
50-
if host == "" {
51-
inspects, err := findDockerHost()
52-
if err == nil {
53-
for _, inspect := range inspects {
54-
endpoint, ok := inspect.Endpoints["docker"]
55-
if ok {
56-
os.Setenv("DOCKER_HOST", endpoint.Host)
57-
break
58-
}
59-
}
45+
if clientOptions.host == "" {
46+
host, err := determineDockerHost()
47+
if err != nil {
48+
return nil, fmt.Errorf("error determining docker host: %w", err)
6049
}
50+
clientOptions.host = host
6151
}
6252

6353
// TODO[md]: we create a client at the top of each cli invocation, the sdk client hits an api which
6454
// adds (a tiny biy of) overead. swap this with a handle that'll lazily initialize a client and ping for health.
6555
// ditto for fetching registry credentials.
66-
client, err := dc.NewClientWithOpts(dc.FromEnv, dc.WithAPIVersionNegotiation())
56+
57+
dockerClientOpts := []dc.Opt{
58+
dc.WithTLSClientConfigFromEnv(),
59+
dc.WithVersionFromEnv(),
60+
dc.WithAPIVersionNegotiation(),
61+
dc.WithHost(clientOptions.host),
62+
}
63+
64+
client, err := dc.NewClientWithOpts(dockerClientOpts...)
6765
if err != nil {
6866
return nil, fmt.Errorf("error creating docker client: %w", err)
6967
}
@@ -628,25 +626,3 @@ func shouldAttachStdin(stdin io.Reader) (attach bool, tty bool) {
628626
// the container to attach stdin and keep open
629627
return true, true
630628
}
631-
632-
func findDockerHost() ([]command.ContextInspect, error) {
633-
dockerCmd := DockerCommandFromEnvironment()
634-
cmd := exec.Command(dockerCmd, "context", "inspect")
635-
636-
// Create a buffer to capture the standard output
637-
var out bytes.Buffer
638-
cmd.Stdout = &out
639-
640-
// Run the command
641-
err := cmd.Run()
642-
if err != nil {
643-
return nil, err
644-
}
645-
646-
var resp []command.ContextInspect
647-
if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
648-
return nil, fmt.Errorf("error unmarshaling inspect response: %w", err)
649-
}
650-
651-
return resp, nil
652-
}

pkg/docker/command/context_inspect.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

pkg/docker/host.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package docker
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
dconfig "github.com/docker/cli/cli/config"
8+
dctxdocker "github.com/docker/cli/cli/context/docker"
9+
dctxstore "github.com/docker/cli/cli/context/store"
10+
11+
"github.com/replicate/cog/pkg/util/console"
12+
)
13+
14+
// determineDockerHost returns the host to use for the docker client.
15+
// It first checks the DOCKER_HOST environment variable, then the docker context, and finally the system default.
16+
func determineDockerHost() (string, error) {
17+
// 1) if DOCKER_HOST is set, use it
18+
if host := os.Getenv("DOCKER_HOST"); host != "" {
19+
return host, nil
20+
}
21+
22+
// 2) try to get a host from the docker context. Use DOCKER_CONTEXT if set, otherwise check the current context
23+
if host, err := dockerHostFromContext(os.Getenv("DOCKER_CONTEXT")); err != nil {
24+
console.Warnf("error finding docker host from context: %v", err)
25+
26+
// if DOCKER_CONTEXT was explicitly set, return an error since the user probably expects that context to be used
27+
if os.Getenv("DOCKER_CONTEXT") != "" {
28+
return "", err
29+
}
30+
} else if host != "" {
31+
return host, nil
32+
}
33+
34+
// 3) if we couldn't get a host from env or context, fallback to the system default
35+
return defaultDockerHost, nil
36+
}
37+
38+
func dockerHostFromContext(contextName string) (string, error) {
39+
if contextName == "" {
40+
cf, err := dconfig.Load(dconfig.Dir())
41+
if err != nil {
42+
return "", err
43+
}
44+
contextName = cf.CurrentContext
45+
}
46+
47+
typeGetter := func() any { return &dctxdocker.EndpointMeta{} }
48+
storeConfig := dctxstore.NewConfig(typeGetter, dctxstore.EndpointTypeGetter(dctxdocker.DockerEndpoint, typeGetter))
49+
50+
store := dctxstore.New(dconfig.ContextStoreDir(), storeConfig)
51+
meta, err := store.GetMetadata(contextName)
52+
if err != nil {
53+
return "", err
54+
}
55+
56+
endpoint, ok := meta.Endpoints[dctxdocker.DockerEndpoint]
57+
if !ok {
58+
return "", fmt.Errorf("no docker endpoints found for context %s", contextName)
59+
}
60+
61+
dockerEPMeta, ok := endpoint.(dctxdocker.EndpointMeta)
62+
if !ok {
63+
return "", fmt.Errorf("invalid context config: %v", endpoint)
64+
}
65+
66+
if dockerEPMeta.Host == "" {
67+
return "", fmt.Errorf("no host found for context %s", contextName)
68+
}
69+
70+
return dockerEPMeta.Host, nil
71+
}

pkg/docker/host_unix.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !windows
2+
3+
package docker
4+
5+
const (
6+
defaultDockerHost = "unix:///var/run/docker.sock"
7+
)

pkg/docker/host_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package docker
2+
3+
const (
4+
defaultDockerHost = "npipe:////.pipe/docker_engine"
5+
)

pkg/docker/options.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "github.com/docker/docker/api/types/registry"
44

55
type clientOptions struct {
66
authConfigs map[string]registry.AuthConfig
7+
host string
78
}
89

910
type Option func(*clientOptions)
@@ -13,3 +14,9 @@ func WithAuthConfig(authConfig registry.AuthConfig) Option {
1314
o.authConfigs[authConfig.ServerAddress] = authConfig
1415
}
1516
}
17+
18+
func WithHost(host string) Option {
19+
return func(o *clientOptions) {
20+
o.host = host
21+
}
22+
}

0 commit comments

Comments
 (0)