Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit 67e5699

Browse files
authored
Merge pull request #132 from docker/package-without-push
docker model package without --push
2 parents 085e1d4 + 0abe90c commit 67e5699

File tree

199 files changed

+20842
-304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

199 files changed

+20842
-304
lines changed

commands/package.go

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,39 @@ package commands
22

33
import (
44
"bufio"
5+
"context"
56
"encoding/json"
67
"fmt"
78
"html"
89
"io"
910
"path/filepath"
1011

11-
"github.com/docker/model-cli/commands/completion"
12-
"github.com/docker/model-cli/desktop"
1312
"github.com/docker/model-distribution/builder"
1413
"github.com/docker/model-distribution/registry"
14+
"github.com/docker/model-distribution/tarball"
15+
"github.com/docker/model-distribution/types"
16+
"github.com/google/go-containerregistry/pkg/name"
1517
"github.com/spf13/cobra"
18+
19+
"github.com/docker/model-cli/commands/completion"
20+
"github.com/docker/model-cli/desktop"
1621
)
1722

1823
func newPackagedCmd() *cobra.Command {
1924
var opts packageOptions
2025

2126
c := &cobra.Command{
22-
Use: "package --gguf <path> [--license <path>...] [--context-size <tokens>] --push TARGET",
23-
Short: "Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry",
27+
Use: "package --gguf <path> [--license <path>...] [--context-size <tokens>] [--push] MODEL",
28+
Short: "Package a GGUF file into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified",
2429
Args: func(cmd *cobra.Command, args []string) error {
2530
if len(args) != 1 {
2631
return fmt.Errorf(
2732
"'docker model package' requires 1 argument.\n\n"+
28-
"Usage: %s\n\n"+
33+
"Usage: docker model %s\n\n"+
2934
"See 'docker model package --help' for more information",
3035
cmd.Use,
3136
)
3237
}
33-
if opts.push != true {
34-
return fmt.Errorf(
35-
"This version of 'docker model package' requires --push and will write the resulting package directly to the registry.\n\n" +
36-
"See 'docker model package --help' for more information",
37-
)
38-
}
3938
if opts.ggufPath == "" {
4039
return fmt.Errorf(
4140
"GGUF path is required.\n\n" +
@@ -62,7 +61,8 @@ func newPackagedCmd() *cobra.Command {
6261
return nil
6362
},
6463
RunE: func(cmd *cobra.Command, args []string) error {
65-
if err := packageModel(cmd, args[0], opts); err != nil {
64+
opts.tag = args[0]
65+
if err := packageModel(cmd, opts); err != nil {
6666
cmd.PrintErrln("Failed to package model")
6767
return fmt.Errorf("package model: %w", err)
6868
}
@@ -73,7 +73,7 @@ func newPackagedCmd() *cobra.Command {
7373

7474
c.Flags().StringVar(&opts.ggufPath, "gguf", "", "absolute path to gguf file (required)")
7575
c.Flags().StringArrayVarP(&opts.licensePaths, "license", "l", nil, "absolute path to a license file")
76-
c.Flags().BoolVar(&opts.push, "push", false, "push to registry (required)")
76+
c.Flags().BoolVar(&opts.push, "push", false, "push to registry (if not set, the model is loaded into the Model Runner content store.")
7777
c.Flags().Uint64Var(&opts.contextSize, "context-size", 0, "context size in tokens")
7878
return c
7979
}
@@ -83,13 +83,21 @@ type packageOptions struct {
8383
licensePaths []string
8484
push bool
8585
contextSize uint64
86+
tag string
8687
}
8788

88-
func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
89-
cmd.PrintErrf("Packaging model %q\n", tag)
90-
target, err := registry.NewClient(
91-
registry.WithUserAgent("docker-model-cli/" + desktop.Version),
92-
).NewTarget(tag)
89+
func packageModel(cmd *cobra.Command, opts packageOptions) error {
90+
var (
91+
target builder.Target
92+
err error
93+
)
94+
if opts.push {
95+
target, err = registry.NewClient(
96+
registry.WithUserAgent("docker-model-cli/" + desktop.Version),
97+
).NewTarget(opts.tag)
98+
} else {
99+
target, err = newModelRunnerTarget(desktopClient, opts.tag)
100+
}
93101
if err != nil {
94102
return err
95103
}
@@ -116,8 +124,11 @@ func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
116124
}
117125
}
118126

119-
// Write the artifact to the registry
120-
cmd.PrintErrln("Pushing to registry...")
127+
if opts.push {
128+
cmd.PrintErrln("Pushing model to registry...")
129+
} else {
130+
cmd.PrintErrln("Loading model to Model Runner...")
131+
}
121132
pr, pw := io.Pipe()
122133
done := make(chan error, 1)
123134
go func() {
@@ -147,8 +158,70 @@ func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
147158
cmd.PrintErrln("Error streaming progress:", err)
148159
}
149160
if err := <-done; err != nil {
150-
return fmt.Errorf("push: %w", err)
161+
if opts.push {
162+
return fmt.Errorf("failed to save packaged model: %w", err)
163+
}
164+
return fmt.Errorf("failed to load packaged model: %w", err)
165+
}
166+
167+
if opts.push {
168+
cmd.PrintErrln("Model pushed successfully")
169+
} else {
170+
cmd.PrintErrln("Model loaded successfully")
171+
}
172+
return nil
173+
}
174+
175+
// modelRunnerTarget loads model to Docker Model Runner via models/load endpoint
176+
type modelRunnerTarget struct {
177+
client *desktop.Client
178+
tag name.Tag
179+
}
180+
181+
func newModelRunnerTarget(client *desktop.Client, tag string) (*modelRunnerTarget, error) {
182+
target := &modelRunnerTarget{
183+
client: client,
184+
}
185+
if tag != "" {
186+
var err error
187+
target.tag, err = name.NewTag(tag)
188+
if err != nil {
189+
return nil, fmt.Errorf("invalid tag: %w", err)
190+
}
191+
}
192+
return target, nil
193+
}
194+
195+
func (t *modelRunnerTarget) Write(ctx context.Context, mdl types.ModelArtifact, progressWriter io.Writer) error {
196+
pr, pw := io.Pipe()
197+
errCh := make(chan error, 1)
198+
go func() {
199+
defer pw.Close()
200+
target, err := tarball.NewTarget(pw)
201+
if err != nil {
202+
errCh <- err
203+
return
204+
}
205+
errCh <- target.Write(ctx, mdl, progressWriter)
206+
}()
207+
208+
loadErr := t.client.LoadModel(ctx, pr)
209+
writeErr := <-errCh
210+
211+
if loadErr != nil {
212+
return fmt.Errorf("loading model archive: %w", loadErr)
213+
}
214+
if writeErr != nil {
215+
return fmt.Errorf("writing model archive: %w", writeErr)
216+
}
217+
id, err := mdl.ID()
218+
if err != nil {
219+
return fmt.Errorf("get model ID: %w", err)
220+
}
221+
if t.tag.String() != "" {
222+
if err := desktopClient.Tag(id, parseRepo(t.tag), t.tag.TagStr()); err != nil {
223+
return fmt.Errorf("tag model: %w", err)
224+
}
151225
}
152-
cmd.PrintErrln("Model pushed successfully")
153226
return nil
154227
}

desktop/desktop.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package desktop
33
import (
44
"bufio"
55
"bytes"
6+
"context"
67
"encoding/json"
78
"fmt"
89
"html"
@@ -704,3 +705,25 @@ func (c *Client) Tag(source, targetRepo, targetTag string) error {
704705

705706
return nil
706707
}
708+
709+
func (c *Client) LoadModel(ctx context.Context, r io.Reader) error {
710+
loadPath := fmt.Sprintf("%s/load", inference.ModelsPrefix)
711+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.modelRunner.URL(loadPath), r)
712+
if err != nil {
713+
return fmt.Errorf("failed to create request: %w", err)
714+
}
715+
req.Header.Set("Content-Type", "application/x-tar")
716+
req.Header.Set("User-Agent", "docker-model-cli/"+Version)
717+
718+
resp, err := c.modelRunner.Client().Do(req)
719+
if err != nil {
720+
return c.handleQueryError(err, loadPath)
721+
}
722+
defer resp.Body.Close()
723+
724+
body, _ := io.ReadAll(resp.Body)
725+
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
726+
return fmt.Errorf("load failed with status %s: %s", resp.Status, string(body))
727+
}
728+
return nil
729+
}

docs/reference/docker_model_package.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
command: docker model package
22
short: |
3-
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
3+
Package a GGUF file into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified
44
long: |
5-
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
6-
usage: docker model package --gguf <path> [--license <path>...] [--context-size <tokens>] --push TARGET
5+
Package a GGUF file into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified
6+
usage: docker model package --gguf <path> [--license <path>...] [--context-size <tokens>] [--push] MODEL
77
pname: docker model
88
plink: docker_model.yaml
99
options:
@@ -40,7 +40,8 @@ options:
4040
- option: push
4141
value_type: bool
4242
default_value: "false"
43-
description: push to registry (required)
43+
description: |
44+
push to registry (if not set, the model is loaded into the Model Runner content store.
4445
deprecated: false
4546
hidden: false
4647
experimental: false

docs/reference/model.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ Docker Model Runner (EXPERIMENTAL)
55

66
### Subcommands
77

8-
| Name | Description |
9-
|:------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
10-
| [`df`](model_df.md) | Show Docker Model Runner disk usage |
11-
| [`inspect`](model_inspect.md) | Display detailed information on one model |
12-
| [`install-runner`](model_install-runner.md) | Install Docker Model Runner (Docker Engine only) |
13-
| [`list`](model_list.md) | List the models pulled to your local environment |
14-
| [`logs`](model_logs.md) | Fetch the Docker Model Runner logs |
15-
| [`package`](model_package.md) | Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry |
16-
| [`ps`](model_ps.md) | List running models |
17-
| [`pull`](model_pull.md) | Pull a model from Docker Hub or HuggingFace to your local environment |
18-
| [`push`](model_push.md) | Push a model to Docker Hub |
19-
| [`rm`](model_rm.md) | Remove local models downloaded from Docker Hub |
20-
| [`run`](model_run.md) | Run a model and interact with it using a submitted prompt or chat mode |
21-
| [`status`](model_status.md) | Check if the Docker Model Runner is running |
22-
| [`tag`](model_tag.md) | Tag a model |
23-
| [`uninstall-runner`](model_uninstall-runner.md) | Uninstall Docker Model Runner |
24-
| [`unload`](model_unload.md) | Unload running models |
25-
| [`version`](model_version.md) | Show the Docker Model Runner version |
8+
| Name | Description |
9+
|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------|
10+
| [`df`](model_df.md) | Show Docker Model Runner disk usage |
11+
| [`inspect`](model_inspect.md) | Display detailed information on one model |
12+
| [`install-runner`](model_install-runner.md) | Install Docker Model Runner (Docker Engine only) |
13+
| [`list`](model_list.md) | List the models pulled to your local environment |
14+
| [`logs`](model_logs.md) | Fetch the Docker Model Runner logs |
15+
| [`package`](model_package.md) | Package a GGUF file into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified |
16+
| [`ps`](model_ps.md) | List running models |
17+
| [`pull`](model_pull.md) | Pull a model from Docker Hub or HuggingFace to your local environment |
18+
| [`push`](model_push.md) | Push a model to Docker Hub |
19+
| [`rm`](model_rm.md) | Remove local models downloaded from Docker Hub |
20+
| [`run`](model_run.md) | Run a model and interact with it using a submitted prompt or chat mode |
21+
| [`status`](model_status.md) | Check if the Docker Model Runner is running |
22+
| [`tag`](model_tag.md) | Tag a model |
23+
| [`uninstall-runner`](model_uninstall-runner.md) | Uninstall Docker Model Runner |
24+
| [`unload`](model_unload.md) | Unload running models |
25+
| [`version`](model_version.md) | Show the Docker Model Runner version |
2626

2727

2828

docs/reference/model_package.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# docker model package
22

33
<!---MARKER_GEN_START-->
4-
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
4+
Package a GGUF file into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified
55

66
### Options
77

8-
| Name | Type | Default | Description |
9-
|:------------------|:--------------|:--------|:--------------------------------------|
10-
| `--context-size` | `uint64` | `0` | context size in tokens |
11-
| `--gguf` | `string` | | absolute path to gguf file (required) |
12-
| `-l`, `--license` | `stringArray` | | absolute path to a license file |
13-
| `--push` | `bool` | | push to registry (required) |
8+
| Name | Type | Default | Description |
9+
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------------|
10+
| `--context-size` | `uint64` | `0` | context size in tokens |
11+
| `--gguf` | `string` | | absolute path to gguf file (required) |
12+
| `-l`, `--license` | `stringArray` | | absolute path to a license file |
13+
| `--push` | `bool` | | push to registry (if not set, the model is loaded into the Model Runner content store. |
1414

1515

1616
<!---MARKER_GEN_END-->

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ require (
1111
github.com/docker/docker v28.2.2+incompatible
1212
github.com/docker/go-connections v0.5.0
1313
github.com/docker/go-units v0.5.0
14-
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e
15-
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e
14+
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c
15+
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807
1616
github.com/google/go-containerregistry v0.20.6
1717
github.com/mattn/go-isatty v0.0.17
1818
github.com/nxadm/tail v1.4.8
@@ -43,6 +43,8 @@ require (
4343
github.com/distribution/reference v0.6.0 // indirect
4444
github.com/docker/distribution v2.8.3+incompatible // indirect
4545
github.com/docker/docker-credential-helpers v0.9.3 // indirect
46+
github.com/elastic/go-sysinfo v1.15.3 // indirect
47+
github.com/elastic/go-windows v1.0.2 // indirect
4648
github.com/felixge/httpsnoop v1.0.4 // indirect
4749
github.com/fsnotify/fsnotify v1.9.0 // indirect
4850
github.com/fvbommel/sortorder v1.1.0 // indirect
@@ -77,6 +79,7 @@ require (
7779
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
7880
github.com/prometheus/client_model v0.6.2 // indirect
7981
github.com/prometheus/common v0.65.0 // indirect
82+
github.com/prometheus/procfs v0.15.1 // indirect
8083
github.com/rivo/uniseg v0.4.7 // indirect
8184
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
8285
github.com/russross/blackfriday/v2 v2.1.0 // indirect

go.sum

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,15 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz
7878
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
7979
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
8080
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
81-
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e h1:qBkjP4A20f3RXvtstitIPiStQ4p+bK8xcjosrXLBQZ0=
82-
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
83-
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e h1:oafd84kAFBgv/DAYgtXGLkC1KmRpDN+7G3be5+2+hA0=
84-
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e/go.mod h1:QmSoUNAbqolMY1Aq9DaC+sR/M/OPga0oCT/DBA1z9ow=
81+
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c h1:w9MekYamXmWLe9ZWXWgNXJ7BLDDemXwB8WcF7wzHF5Q=
82+
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
83+
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807 h1:02vImD8wqUDv6VJ2cBLbqzbjn17IMYEi4ileCEjXMQ8=
84+
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807/go.mod h1:rCzRjRXJ42E8JVIA69E9hErJVV5mnUpWdJ2POsktfRs=
8585
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
86+
github.com/elastic/go-sysinfo v1.15.3 h1:W+RnmhKFkqPTCRoFq2VCTmsT4p/fwpo+3gKNQsn1XU0=
87+
github.com/elastic/go-sysinfo v1.15.3/go.mod h1:K/cNrqYTDrSoMh2oDkYEMS2+a72GRxMvNP+GC+vRIlo=
88+
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
89+
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
8690
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
8791
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
8892
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=

0 commit comments

Comments
 (0)