@@ -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+
163265func 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
0 commit comments