Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions internal/runtime/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,40 @@ func probeSocket(candidates ...string) string {
return ""
}

// isVM reports whether the Docker daemon is running inside a VM (e.g., Colima, OrbStack).
// In these cases the socket is remapped inside the VM and the container sees it at
// /var/run/docker.sock even if the CLI connects via a user-scoped socket path.
func (d *DockerRuntime) isVM() bool {
host := d.client.DaemonHost()
if strings.HasPrefix(host, "unix://") {
socketPath := strings.TrimPrefix(host, "unix://")
// Check for known VM-based Docker socket locations
home, _ := os.UserHomeDir()
vmSockets := []string{
filepath.Join(home, ".colima", "default", "docker.sock"),
filepath.Join(home, ".colima", "docker.sock"),
filepath.Join(home, ".orbstack", "run", "docker.sock"),
}
for _, vmSock := range vmSockets {
if socketPath == vmSock {
return true
}
}
}
return false
}

// SocketPath returns the daemon-visible Unix socket path to bind-mount into
// containers so LocalStack can launch nested workloads such as Lambda functions.
// For VM-based Docker (Colima, OrbStack) returns /var/run/docker.sock as the
// socket is remapped inside the VM. For rootless or custom setups, returns the
// actual socket path extracted from the daemon host.
func (d *DockerRuntime) SocketPath() string {
host := d.client.DaemonHost()
if strings.HasPrefix(host, "unix://") {
if d.isVM() {
return "/var/run/docker.sock"
}
return strings.TrimPrefix(host, "unix://")
}
return ""
Expand Down
76 changes: 71 additions & 5 deletions internal/runtime/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,26 @@ func TestProbeSocket_ReturnsEmptyForNoCandidates(t *testing.T) {
}

func TestSocketPath_ExtractsUnixPath(t *testing.T) {
cli, err := client.NewClientWithOpts(client.WithHost("unix:///home/user/.colima/default/docker.sock"))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}

assert.Equal(t, "/home/user/.colima/default/docker.sock", rt.SocketPath())
t.Run("standard socket returns daemon path", func(t *testing.T) {
cli, err := client.NewClientWithOpts(client.WithHost("unix:///var/run/docker.sock"))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, "/var/run/docker.sock", rt.SocketPath())
})

t.Run("non-standard socket returns daemon path", func(t *testing.T) {
cli, err := client.NewClientWithOpts(client.WithHost("unix:///home/user/.colima/default/docker.sock"))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, "/home/user/.colima/default/docker.sock", rt.SocketPath())
})

t.Run("orbstack socket returns daemon path", func(t *testing.T) {
cli, err := client.NewClientWithOpts(client.WithHost("unix:///Users/user/.orbstack/run/docker.sock"))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, "/Users/user/.orbstack/run/docker.sock", rt.SocketPath())
})
}

func TestSocketPath_ReturnsEmptyForTCPHost(t *testing.T) {
Expand All @@ -56,6 +71,57 @@ func TestSocketPath_ReturnsEmptyForTCPHost(t *testing.T) {
assert.Equal(t, "", rt.SocketPath())
}

func TestSocketPath_VMDetection(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)

t.Run("colima socket exists returns remapped path", func(t *testing.T) {
colimaSock := filepath.Join(home, ".colima", "default", "docker.sock")
require.NoError(t, os.MkdirAll(filepath.Dir(colimaSock), 0o755))
f, err := os.Create(colimaSock)
require.NoError(t, err)
require.NoError(t, f.Close())
t.Cleanup(func() {
require.NoError(t, os.Remove(colimaSock))
})

cli, err := client.NewClientWithOpts(client.WithHost("unix://" + colimaSock))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, "/var/run/docker.sock", rt.SocketPath())
})

t.Run("orbstack socket exists returns remapped path", func(t *testing.T) {
orbstackSock := filepath.Join(home, ".orbstack", "run", "docker.sock")
require.NoError(t, os.MkdirAll(filepath.Dir(orbstackSock), 0o755))
f, err := os.Create(orbstackSock)
require.NoError(t, err)
require.NoError(t, f.Close())
t.Cleanup(func() {
require.NoError(t, os.Remove(orbstackSock))
})

cli, err := client.NewClientWithOpts(client.WithHost("unix://" + orbstackSock))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, "/var/run/docker.sock", rt.SocketPath())
})

t.Run("rootless socket exists returns actual path", func(t *testing.T) {
// Use a non-VM socket path (short path to avoid Docker client limit)
rootlessSock := "/tmp/lstk-docker.sock"
require.NoError(t, os.WriteFile(rootlessSock, nil, 0o600))
t.Cleanup(func() {
require.NoError(t, os.Remove(rootlessSock))
})

cli, err := client.NewClientWithOpts(client.WithHost("unix://" + rootlessSock))
require.NoError(t, err)
rt := &DockerRuntime{client: cli}
assert.Equal(t, rootlessSock, rt.SocketPath())
})
}

func TestWindowsDockerStartCommand_DockerAvailable(t *testing.T) {
lookPath := func(string) (string, error) { return "/usr/bin/docker", nil }
assert.Equal(t, "docker desktop start", windowsDockerStartCommand(func(string) string { return "" }, lookPath))
Expand Down
12 changes: 12 additions & 0 deletions test/integration/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ func TestStartCommandSetsUpContainerCorrectly(t *testing.T) {

assert.True(t, hasBindTarget(inspect.HostConfig.Binds, "/var/run/docker.sock"),
"expected Docker socket bind mount to /var/run/docker.sock, got: %v", inspect.HostConfig.Binds)
assert.True(t, hasBindSource(inspect.HostConfig.Binds, "/var/run/docker.sock"),
"expected Docker socket bind mount from /var/run/docker.sock, got: %v", inspect.HostConfig.Binds)

envVars := containerEnvToMap(inspect.Config.Env)
assert.Equal(t, "unix:///var/run/docker.sock", envVars["DOCKER_HOST"])
Expand Down Expand Up @@ -247,6 +249,16 @@ func hasBindTarget(binds []string, containerPath string) bool {
return false
}

func hasBindSource(binds []string, hostPath string) bool {
for _, b := range binds {
parts := strings.Split(b, ":")
if len(parts) >= 2 && parts[0] == hostPath {
return true
}
}
return false
}

func cleanup() {
ctx := context.Background()
_ = dockerClient.ContainerStop(ctx, containerName, container.StopOptions{})
Expand Down