diff --git a/cmd/cli/commands/install-runner.go b/cmd/cli/commands/install-runner.go index 6878e3164..62fca1079 100644 --- a/cmd/cli/commands/install-runner.go +++ b/cmd/cli/commands/install-runner.go @@ -127,12 +127,15 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta // Create the model runner container. port := uint16(standalone.DefaultControllerPortMoby) + // For auto-installation, always bind to localhost for security. + // Users can run install-runner explicitly with --host to change this. + host := "127.0.0.1" environment := "moby" if engineKind == types.ModelRunnerEngineKindCloud { port = standalone.DefaultControllerPortCloud environment = "cloud" } - if err := standalone.CreateControllerContainer(ctx, dockerClient, port, environment, false, gpu, modelStorageVolume, printer, engineKind); err != nil { + if err := standalone.CreateControllerContainer(ctx, dockerClient, port, host, environment, false, gpu, modelStorageVolume, printer, engineKind); err != nil { return nil, fmt.Errorf("unable to initialize standalone model runner container: %w", err) } @@ -159,6 +162,7 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta func newInstallRunner() *cobra.Command { var port uint16 + var host string var gpuMode string var doNotTrack bool c := &cobra.Command{ @@ -245,7 +249,7 @@ func newInstallRunner() *cobra.Command { return fmt.Errorf("unable to initialize standalone model storage: %w", err) } // Create the model runner container. - if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil { + if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, host, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil { return fmt.Errorf("unable to initialize standalone model runner container: %w", err) } @@ -256,6 +260,7 @@ func newInstallRunner() *cobra.Command { } c.Flags().Uint16Var(&port, "port", 0, "Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode)") + c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner") c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)") c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner") return c diff --git a/cmd/cli/commands/install-runner_test.go b/cmd/cli/commands/install-runner_test.go new file mode 100644 index 000000000..bce0d8131 --- /dev/null +++ b/cmd/cli/commands/install-runner_test.go @@ -0,0 +1,96 @@ +package commands + +import ( + "testing" +) + +func TestInstallRunnerHostFlag(t *testing.T) { + // Create the install-runner command + cmd := newInstallRunner() + + // Verify the --host flag exists + hostFlag := cmd.Flags().Lookup("host") + if hostFlag == nil { + t.Fatal("--host flag not found") + } + + // Verify the default value + if hostFlag.DefValue != "127.0.0.1" { + t.Errorf("Expected default host value to be '127.0.0.1', got '%s'", hostFlag.DefValue) + } + + // Verify the flag type + if hostFlag.Value.Type() != "string" { + t.Errorf("Expected host flag type to be 'string', got '%s'", hostFlag.Value.Type()) + } + + // Test setting the flag value + testCases := []struct { + name string + value string + }{ + {"localhost", "127.0.0.1"}, + {"all interfaces", "0.0.0.0"}, + {"specific IP", "192.168.1.100"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Reset the command for each test + cmd := newInstallRunner() + err := cmd.Flags().Set("host", tc.value) + if err != nil { + t.Errorf("Failed to set host flag to '%s': %v", tc.value, err) + } + + // Verify the value was set + hostValue, err := cmd.Flags().GetString("host") + if err != nil { + t.Errorf("Failed to get host flag value: %v", err) + } + if hostValue != tc.value { + t.Errorf("Expected host value to be '%s', got '%s'", tc.value, hostValue) + } + }) + } +} + +func TestInstallRunnerCommandFlags(t *testing.T) { + cmd := newInstallRunner() + + // Verify all expected flags exist + expectedFlags := []string{"port", "host", "gpu", "do-not-track"} + for _, flagName := range expectedFlags { + if cmd.Flags().Lookup(flagName) == nil { + t.Errorf("Expected flag '--%s' not found", flagName) + } + } +} + +func TestInstallRunnerCommandType(t *testing.T) { + cmd := newInstallRunner() + + // Verify command properties + if cmd.Use != "install-runner" { + t.Errorf("Expected command Use to be 'install-runner', got '%s'", cmd.Use) + } + + if cmd.Short != "Install Docker Model Runner (Docker Engine only)" { + t.Errorf("Unexpected command Short description: %s", cmd.Short) + } + + // Verify RunE is set + if cmd.RunE == nil { + t.Error("Expected RunE to be set") + } +} + +func TestInstallRunnerValidArgsFunction(t *testing.T) { + cmd := newInstallRunner() + + // The install-runner command should not accept any arguments + // So ValidArgsFunction should be set to handle no arguments + if cmd.ValidArgsFunction == nil { + t.Error("Expected ValidArgsFunction to be set") + } +} diff --git a/cmd/cli/docs/reference/model_install-runner.md b/cmd/cli/docs/reference/model_install-runner.md index bbbd38af2..e8911ccaa 100644 --- a/cmd/cli/docs/reference/model_install-runner.md +++ b/cmd/cli/docs/reference/model_install-runner.md @@ -5,11 +5,12 @@ Install Docker Model Runner (Docker Engine only) ### Options -| Name | Type | Default | Description | -|:-----------------|:---------|:--------|:---------------------------------------------------------------------------------------------------| -| `--do-not-track` | `bool` | | Do not track models usage in Docker Model Runner | -| `--gpu` | `string` | `auto` | Specify GPU support (none\|auto\|cuda) | -| `--port` | `uint16` | `0` | Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode) | +| Name | Type | Default | Description | +|:-----------------|:---------|:------------|:---------------------------------------------------------------------------------------------------| +| `--do-not-track` | `bool` | | Do not track models usage in Docker Model Runner | +| `--gpu` | `string` | `auto` | Specify GPU support (none\|auto\|cuda) | +| `--host` | `string` | `127.0.0.1` | Host address to bind Docker Model Runner | +| `--port` | `uint16` | `0` | Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode) | diff --git a/cmd/cli/pkg/standalone/containers.go b/cmd/cli/pkg/standalone/containers.go index d28d6c42a..aed796ea1 100644 --- a/cmd/cli/pkg/standalone/containers.go +++ b/cmd/cli/pkg/standalone/containers.go @@ -218,7 +218,7 @@ func ensureContainerStarted(ctx context.Context, dockerClient client.ContainerAP } // CreateControllerContainer creates and starts a controller container. -func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, port uint16, environment string, doNotTrack bool, gpu gpupkg.GPUSupport, modelStorageVolume string, printer StatusPrinter, engineKind types.ModelRunnerEngineKind) error { +func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, port uint16, host string, environment string, doNotTrack bool, gpu gpupkg.GPUSupport, modelStorageVolume string, printer StatusPrinter, engineKind types.ModelRunnerEngineKind) error { // Determine the target image. var imageName string switch gpu { @@ -260,11 +260,14 @@ func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, Name: "always", }, } - portBindings := []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: portStr}} + portBindings := []nat.PortBinding{{HostIP: host, HostPort: portStr}} if os.Getenv("_MODEL_RUNNER_TREAT_DESKTOP_AS_MOBY") != "1" { // Don't bind the bridge gateway IP if we're treating Docker Desktop as Moby. - if bridgeGatewayIP, err := determineBridgeGatewayIP(ctx, dockerClient); err == nil && bridgeGatewayIP != "" { - portBindings = append(portBindings, nat.PortBinding{HostIP: bridgeGatewayIP, HostPort: portStr}) + // Only add bridge gateway IP binding if host is 127.0.0.1 + if host == "127.0.0.1" { + if bridgeGatewayIP, err := determineBridgeGatewayIP(ctx, dockerClient); err == nil && bridgeGatewayIP != "" { + portBindings = append(portBindings, nat.PortBinding{HostIP: bridgeGatewayIP, HostPort: portStr}) + } } } hostConfig.PortBindings = nat.PortMap{