Skip to content

Commit 28b9194

Browse files
committed
Add start-runner, restart-runner and stop-runner commands
To start, restart and stop container without pulling or removing the image. Signed-off-by: Eric Curtin <[email protected]>
1 parent 38a800e commit 28b9194

File tree

14 files changed

+335
-135
lines changed

14 files changed

+335
-135
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
model-distribution-tool
33
model-runner
44
model-runner.sock
5+
docker-model
56
# Default MODELS_PATH in Makefile
67
models-store/
78
# Default MODELS_PATH in mdltool

cmd/cli/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ Run `./model --help` to see all commands and options.
3333

3434
### Common Commands
3535
- `model install-runner` — Install the Docker Model Runner
36+
- `model start-runner` — Start the Docker Model Runner
37+
- `model stop-runner` — Stop the Docker Model Runner
38+
- `model restart-runner` — Restart the Docker Model Runner
3639
- `model run MODEL [PROMPT]` — Run a model with a prompt or enter chat mode
3740
- `model list` — List available models
3841
- `model package --gguf <path> --push <target>` — Package and push a model

cmd/cli/commands/install-runner.go

Lines changed: 111 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,108 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta
160160
return inspectStandaloneRunner(container), nil
161161
}
162162

163+
// runnerOptions holds common configuration for install/start commands
164+
type runnerOptions struct {
165+
port uint16
166+
host string
167+
gpuMode string
168+
doNotTrack bool
169+
pullImage bool
170+
}
171+
172+
// runInstallOrStart is shared logic for install-runner and start-runner commands
173+
func runInstallOrStart(cmd *cobra.Command, opts runnerOptions) error {
174+
// Ensure that we're running in a supported model runner context.
175+
engineKind := modelRunner.EngineKind()
176+
if engineKind == types.ModelRunnerEngineKindDesktop {
177+
// TODO: We may eventually want to auto-forward this to
178+
// docker desktop enable model-runner, but we should first make
179+
// sure the CLI flags match.
180+
cmd.Println("Standalone installation not supported with Docker Desktop")
181+
cmd.Println("Use `docker desktop enable model-runner` instead")
182+
return nil
183+
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
184+
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
185+
return nil
186+
}
187+
188+
port := opts.port
189+
if port == 0 {
190+
// Use "0" as a sentinel default flag value so it's not displayed automatically.
191+
// The default values are written in the usage string.
192+
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
193+
port = standalone.DefaultControllerPortMoby
194+
}
195+
// HACK: If we're in a Cloud context, then we need to use a
196+
// different default port because it conflicts with Docker Desktop's
197+
// default model runner host-side port. Unfortunately we can't make
198+
// the port flag default dynamic (at least not easily) because of
199+
// when context detection happens. So assume that a default value
200+
// indicates that we want the Cloud default port. This is less
201+
// problematic in Cloud since the UX there is mostly invisible.
202+
if engineKind == types.ModelRunnerEngineKindCloud &&
203+
port == standalone.DefaultControllerPortMoby {
204+
port = standalone.DefaultControllerPortCloud
205+
}
206+
207+
// Set the appropriate environment.
208+
environment := "moby"
209+
if engineKind == types.ModelRunnerEngineKindCloud {
210+
environment = "cloud"
211+
}
212+
213+
// Create a Docker client for the active context.
214+
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
215+
if err != nil {
216+
return fmt.Errorf("failed to create Docker client: %w", err)
217+
}
218+
219+
// Check if an active model runner container already exists.
220+
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
221+
return err
222+
} else if ctrID != "" {
223+
if ctrName != "" {
224+
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
225+
} else {
226+
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
227+
}
228+
return nil
229+
}
230+
231+
// Determine GPU support.
232+
var gpu gpupkg.GPUSupport
233+
if opts.gpuMode == "auto" {
234+
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
235+
if err != nil {
236+
return fmt.Errorf("unable to probe GPU support: %w", err)
237+
}
238+
} else if opts.gpuMode == "cuda" {
239+
gpu = gpupkg.GPUSupportCUDA
240+
} else if opts.gpuMode != "none" {
241+
return fmt.Errorf("unknown GPU specification: %q", opts.gpuMode)
242+
}
243+
244+
// Ensure that we have an up-to-date copy of the image, if requested.
245+
if opts.pullImage {
246+
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
247+
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
248+
}
249+
}
250+
251+
// Ensure that we have a model storage volume.
252+
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
253+
if err != nil {
254+
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
255+
}
256+
// Create the model runner container.
257+
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, opts.host, environment, opts.doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
258+
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
259+
}
260+
261+
// Poll until we get a response from the model runner.
262+
return waitForStandaloneRunnerAfterInstall(cmd.Context())
263+
}
264+
163265
func newInstallRunner() *cobra.Command {
164266
var port uint16
165267
var host string
@@ -169,98 +271,19 @@ func newInstallRunner() *cobra.Command {
169271
Use: "install-runner",
170272
Short: "Install Docker Model Runner (Docker Engine only)",
171273
RunE: func(cmd *cobra.Command, args []string) error {
172-
// Ensure that we're running in a supported model runner context.
173-
engineKind := modelRunner.EngineKind()
174-
if engineKind == types.ModelRunnerEngineKindDesktop {
175-
// TODO: We may eventually want to auto-forward this to
176-
// docker desktop enable model-runner, but we should first make
177-
// sure the CLI flags match.
178-
cmd.Println("Standalone installation not supported with Docker Desktop")
179-
cmd.Println("Use `docker desktop enable model-runner` instead")
180-
return nil
181-
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
182-
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
183-
return nil
184-
}
185-
186-
if port == 0 {
187-
// Use "0" as a sentinel default flag value so it's not displayed automatically.
188-
// The default values are written in the usage string.
189-
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
190-
port = standalone.DefaultControllerPortMoby
191-
}
192-
// HACK: If we're in a Cloud context, then we need to use a
193-
// different default port because it conflicts with Docker Desktop's
194-
// default model runner host-side port. Unfortunately we can't make
195-
// the port flag default dynamic (at least not easily) because of
196-
// when context detection happens. So assume that a default value
197-
// indicates that we want the Cloud default port. This is less
198-
// problematic in Cloud since the UX there is mostly invisible.
199-
if engineKind == types.ModelRunnerEngineKindCloud &&
200-
port == standalone.DefaultControllerPortMoby {
201-
port = standalone.DefaultControllerPortCloud
202-
}
203-
204-
// Set the appropriate environment.
205-
environment := "moby"
206-
if engineKind == types.ModelRunnerEngineKindCloud {
207-
environment = "cloud"
208-
}
209-
210-
// Create a Docker client for the active context.
211-
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
212-
if err != nil {
213-
return fmt.Errorf("failed to create Docker client: %w", err)
214-
}
215-
216-
// Check if an active model runner container already exists.
217-
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
218-
return err
219-
} else if ctrID != "" {
220-
if ctrName != "" {
221-
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
222-
} else {
223-
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
224-
}
225-
return nil
226-
}
227-
228-
// Determine GPU support.
229-
var gpu gpupkg.GPUSupport
230-
if gpuMode == "auto" {
231-
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
232-
if err != nil {
233-
return fmt.Errorf("unable to probe GPU support: %w", err)
234-
}
235-
} else if gpuMode == "cuda" {
236-
gpu = gpupkg.GPUSupportCUDA
237-
} else if gpuMode != "none" {
238-
return fmt.Errorf("unknown GPU specification: %q", gpuMode)
239-
}
240-
241-
// Ensure that we have an up-to-date copy of the image.
242-
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
243-
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
244-
}
245-
246-
// Ensure that we have a model storage volume.
247-
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
248-
if err != nil {
249-
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
250-
}
251-
// Create the model runner container.
252-
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, host, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
253-
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
254-
}
255-
256-
// Poll until we get a response from the model runner.
257-
return waitForStandaloneRunnerAfterInstall(cmd.Context())
274+
return runInstallOrStart(cmd, runnerOptions{
275+
port: port,
276+
host: host,
277+
gpuMode: gpuMode,
278+
doNotTrack: doNotTrack,
279+
pullImage: true,
280+
})
258281
},
259282
ValidArgsFunction: completion.NoComplete,
260283
}
261284
c.Flags().Uint16Var(&port, "port", 0,
262-
"Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode)")
263-
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
285+
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
286+
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
264287
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
265288
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
266289
return c

cmd/cli/commands/restart-runner.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package commands
2+
3+
import (
4+
"github.com/docker/model-runner/cmd/cli/commands/completion"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
func newRestartRunner() *cobra.Command {
9+
var port uint16
10+
var host string
11+
var gpuMode string
12+
var doNotTrack bool
13+
c := &cobra.Command{
14+
Use: "restart-runner",
15+
Short: "Restart Docker Model Runner (Docker Engine only)",
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
// First stop the runner without removing models or images
18+
if err := runUninstallOrStop(cmd, cleanupOptions{
19+
models: false,
20+
removeImages: false,
21+
}); err != nil {
22+
return err
23+
}
24+
25+
// Then start the runner with the provided options
26+
return runInstallOrStart(cmd, runnerOptions{
27+
port: port,
28+
host: host,
29+
gpuMode: gpuMode,
30+
doNotTrack: doNotTrack,
31+
pullImage: false,
32+
})
33+
},
34+
ValidArgsFunction: completion.NoComplete,
35+
}
36+
c.Flags().Uint16Var(&port, "port", 0,
37+
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
38+
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
39+
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
40+
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
41+
return c
42+
}

cmd/cli/commands/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command {
106106
newTagCmd(),
107107
newInstallRunner(),
108108
newUninstallRunner(),
109+
newStartRunner(),
110+
newStopRunner(),
111+
newRestartRunner(),
109112
newConfigureCmd(),
110113
newPSCmd(),
111114
newDFCmd(),

cmd/cli/commands/start-runner.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package commands
2+
3+
import (
4+
"github.com/docker/model-runner/cmd/cli/commands/completion"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
func newStartRunner() *cobra.Command {
9+
var port uint16
10+
var gpuMode string
11+
var doNotTrack bool
12+
c := &cobra.Command{
13+
Use: "start-runner",
14+
Short: "Start Docker Model Runner (Docker Engine only)",
15+
RunE: func(cmd *cobra.Command, args []string) error {
16+
return runInstallOrStart(cmd, runnerOptions{
17+
port: port,
18+
gpuMode: gpuMode,
19+
doNotTrack: doNotTrack,
20+
pullImage: false,
21+
})
22+
},
23+
ValidArgsFunction: completion.NoComplete,
24+
}
25+
c.Flags().Uint16Var(&port, "port", 0,
26+
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
27+
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
28+
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
29+
return c
30+
}

cmd/cli/commands/stop-runner.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package commands
2+
3+
import (
4+
"github.com/docker/model-runner/cmd/cli/commands/completion"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
func newStopRunner() *cobra.Command {
9+
var models bool
10+
c := &cobra.Command{
11+
Use: "stop-runner",
12+
Short: "Stop Docker Model Runner",
13+
RunE: func(cmd *cobra.Command, args []string) error {
14+
return runUninstallOrStop(cmd, cleanupOptions{
15+
models: models,
16+
removeImages: false,
17+
})
18+
},
19+
ValidArgsFunction: completion.NoComplete,
20+
}
21+
c.Flags().BoolVar(&models, "models", false, "Remove model storage volume")
22+
return c
23+
}

0 commit comments

Comments
 (0)