Skip to content

Commit c9119e3

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 <eric.curtin@docker.com>
1 parent cb15122 commit c9119e3

File tree

10 files changed

+299
-125
lines changed

10 files changed

+299
-125
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: 107 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -157,100 +157,121 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta
157157
return inspectStandaloneRunner(container), nil
158158
}
159159

160-
func newInstallRunner() *cobra.Command {
161-
var port uint16
162-
var gpuMode string
163-
var doNotTrack bool
164-
c := &cobra.Command{
165-
Use: "install-runner",
166-
Short: "Install Docker Model Runner (Docker Engine only)",
167-
RunE: func(cmd *cobra.Command, args []string) error {
168-
// Ensure that we're running in a supported model runner context.
169-
engineKind := modelRunner.EngineKind()
170-
if engineKind == types.ModelRunnerEngineKindDesktop {
171-
// TODO: We may eventually want to auto-forward this to
172-
// docker desktop enable model-runner, but we should first make
173-
// sure the CLI flags match.
174-
cmd.Println("Standalone installation not supported with Docker Desktop")
175-
cmd.Println("Use `docker desktop enable model-runner` instead")
176-
return nil
177-
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
178-
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
179-
return nil
180-
}
160+
// runnerOptions holds common configuration for install/start commands
161+
type runnerOptions struct {
162+
port uint16
163+
gpuMode string
164+
doNotTrack bool
165+
pullImage bool
166+
}
181167

182-
if port == 0 {
183-
// Use "0" as a sentinel default flag value so it's not displayed automatically.
184-
// The default values are written in the usage string.
185-
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
186-
port = standalone.DefaultControllerPortMoby
187-
}
188-
// HACK: If we're in a Cloud context, then we need to use a
189-
// different default port because it conflicts with Docker Desktop's
190-
// default model runner host-side port. Unfortunately we can't make
191-
// the port flag default dynamic (at least not easily) because of
192-
// when context detection happens. So assume that a default value
193-
// indicates that we want the Cloud default port. This is less
194-
// problematic in Cloud since the UX there is mostly invisible.
195-
if engineKind == types.ModelRunnerEngineKindCloud &&
196-
port == standalone.DefaultControllerPortMoby {
197-
port = standalone.DefaultControllerPortCloud
198-
}
168+
// runInstallOrStart is shared logic for install-runner and start-runner commands
169+
func runInstallOrStart(cmd *cobra.Command, opts runnerOptions) error {
170+
// Ensure that we're running in a supported model runner context.
171+
engineKind := modelRunner.EngineKind()
172+
if engineKind == types.ModelRunnerEngineKindDesktop {
173+
// TODO: We may eventually want to auto-forward this to
174+
// docker desktop enable model-runner, but we should first make
175+
// sure the CLI flags match.
176+
cmd.Println("Standalone installation not supported with Docker Desktop")
177+
cmd.Println("Use `docker desktop enable model-runner` instead")
178+
return nil
179+
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
180+
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
181+
return nil
182+
}
199183

200-
// Set the appropriate environment.
201-
environment := "moby"
202-
if engineKind == types.ModelRunnerEngineKindCloud {
203-
environment = "cloud"
204-
}
184+
port := opts.port
185+
if port == 0 {
186+
// Use "0" as a sentinel default flag value so it's not displayed automatically.
187+
// The default values are written in the usage string.
188+
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
189+
port = standalone.DefaultControllerPortMoby
190+
}
191+
// HACK: If we're in a Cloud context, then we need to use a
192+
// different default port because it conflicts with Docker Desktop's
193+
// default model runner host-side port. Unfortunately we can't make
194+
// the port flag default dynamic (at least not easily) because of
195+
// when context detection happens. So assume that a default value
196+
// indicates that we want the Cloud default port. This is less
197+
// problematic in Cloud since the UX there is mostly invisible.
198+
if engineKind == types.ModelRunnerEngineKindCloud &&
199+
port == standalone.DefaultControllerPortMoby {
200+
port = standalone.DefaultControllerPortCloud
201+
}
202+
203+
// Set the appropriate environment.
204+
environment := "moby"
205+
if engineKind == types.ModelRunnerEngineKindCloud {
206+
environment = "cloud"
207+
}
208+
209+
// Create a Docker client for the active context.
210+
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
211+
if err != nil {
212+
return fmt.Errorf("failed to create Docker client: %w", err)
213+
}
205214

206-
// Create a Docker client for the active context.
207-
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
208-
if err != nil {
209-
return fmt.Errorf("failed to create Docker client: %w", err)
210-
}
215+
// Check if an active model runner container already exists.
216+
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
217+
return err
218+
} else if ctrID != "" {
219+
if ctrName != "" {
220+
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
221+
} else {
222+
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
223+
}
224+
return nil
225+
}
211226

212-
// Check if an active model runner container already exists.
213-
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
214-
return err
215-
} else if ctrID != "" {
216-
if ctrName != "" {
217-
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
218-
} else {
219-
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
220-
}
221-
return nil
222-
}
227+
// Determine GPU support.
228+
var gpu gpupkg.GPUSupport
229+
if opts.gpuMode == "auto" {
230+
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
231+
if err != nil {
232+
return fmt.Errorf("unable to probe GPU support: %w", err)
233+
}
234+
} else if opts.gpuMode == "cuda" {
235+
gpu = gpupkg.GPUSupportCUDA
236+
} else if opts.gpuMode != "none" {
237+
return fmt.Errorf("unknown GPU specification: %q", opts.gpuMode)
238+
}
223239

224-
// Determine GPU support.
225-
var gpu gpupkg.GPUSupport
226-
if gpuMode == "auto" {
227-
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
228-
if err != nil {
229-
return fmt.Errorf("unable to probe GPU support: %w", err)
230-
}
231-
} else if gpuMode == "cuda" {
232-
gpu = gpupkg.GPUSupportCUDA
233-
} else if gpuMode != "none" {
234-
return fmt.Errorf("unknown GPU specification: %q", gpuMode)
235-
}
240+
// Ensure that we have an up-to-date copy of the image, if requested.
241+
if opts.pullImage {
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+
}
236246

237-
// Ensure that we have an up-to-date copy of the image.
238-
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
239-
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
240-
}
247+
// Ensure that we have a model storage volume.
248+
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
249+
if err != nil {
250+
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
251+
}
252+
// Create the model runner container.
253+
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, environment, opts.doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
254+
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
255+
}
241256

242-
// Ensure that we have a model storage volume.
243-
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
244-
if err != nil {
245-
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
246-
}
247-
// Create the model runner container.
248-
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
249-
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
250-
}
257+
// Poll until we get a response from the model runner.
258+
return waitForStandaloneRunnerAfterInstall(cmd.Context())
259+
}
251260

252-
// Poll until we get a response from the model runner.
253-
return waitForStandaloneRunnerAfterInstall(cmd.Context())
261+
func newInstallRunner() *cobra.Command {
262+
var port uint16
263+
var gpuMode string
264+
var doNotTrack bool
265+
c := &cobra.Command{
266+
Use: "install-runner",
267+
Short: "Install Docker Model Runner (Docker Engine only)",
268+
RunE: func(cmd *cobra.Command, args []string) error {
269+
return runInstallOrStart(cmd, runnerOptions{
270+
port: port,
271+
gpuMode: gpuMode,
272+
doNotTrack: doNotTrack,
273+
pullImage: true,
274+
})
254275
},
255276
ValidArgsFunction: completion.NoComplete,
256277
}

cmd/cli/commands/restart-runner.go

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

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 CE, 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)