Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 95 additions & 22 deletions commands/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,39 @@ package commands

import (
"bufio"
"context"
"encoding/json"
"fmt"
"html"
"io"
"path/filepath"

"github.com/docker/model-cli/commands/completion"
"github.com/docker/model-cli/desktop"
"github.com/docker/model-distribution/builder"
"github.com/docker/model-distribution/registry"
"github.com/docker/model-distribution/tarball"
"github.com/docker/model-distribution/types"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"

"github.com/docker/model-cli/commands/completion"
"github.com/docker/model-cli/desktop"
)

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

c := &cobra.Command{
Use: "package --gguf <path> [--license <path>...] [--context-size <tokens>] --push TARGET",
Short: "Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry",
Use: "package --gguf <path> [--license <path>...] [--context-size <tokens>] [--push] MODEL",
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",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf(
"'docker model package' requires 1 argument.\n\n"+
"Usage: %s\n\n"+
"Usage: docker model %s\n\n"+
"See 'docker model package --help' for more information",
cmd.Use,
)
}
if opts.push != true {
return fmt.Errorf(
"This version of 'docker model package' requires --push and will write the resulting package directly to the registry.\n\n" +
"See 'docker model package --help' for more information",
)
}
if opts.ggufPath == "" {
return fmt.Errorf(
"GGUF path is required.\n\n" +
Expand All @@ -62,7 +61,8 @@ func newPackagedCmd() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := packageModel(cmd, args[0], opts); err != nil {
opts.tag = args[0]
if err := packageModel(cmd, opts); err != nil {
cmd.PrintErrln("Failed to package model")
return fmt.Errorf("package model: %w", err)
}
Expand All @@ -73,7 +73,7 @@ func newPackagedCmd() *cobra.Command {

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

func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
cmd.PrintErrf("Packaging model %q\n", tag)
target, err := registry.NewClient(
registry.WithUserAgent("docker-model-cli/" + desktop.Version),
).NewTarget(tag)
func packageModel(cmd *cobra.Command, opts packageOptions) error {
var (
target builder.Target
err error
)
if opts.push {
target, err = registry.NewClient(
registry.WithUserAgent("docker-model-cli/" + desktop.Version),
).NewTarget(opts.tag)
} else {
target, err = newModelRunnerTarget(desktopClient, opts.tag)
}
if err != nil {
return err
}
Expand All @@ -116,8 +124,11 @@ func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
}
}

// Write the artifact to the registry
cmd.PrintErrln("Pushing to registry...")
if opts.push {
cmd.PrintErrln("Pushing model to registry...")
} else {
cmd.PrintErrln("Loading model to Model Runner...")
}
pr, pw := io.Pipe()
done := make(chan error, 1)
go func() {
Expand Down Expand Up @@ -147,8 +158,70 @@ func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error {
cmd.PrintErrln("Error streaming progress:", err)
}
if err := <-done; err != nil {
return fmt.Errorf("push: %w", err)
if opts.push {
return fmt.Errorf("failed to save packaged model: %w", err)
}
return fmt.Errorf("failed to load packaged model: %w", err)
}

if opts.push {
cmd.PrintErrln("Model pushed successfully")
} else {
cmd.PrintErrln("Model loaded successfully")
}
return nil
}

// modelRunnerTarget loads model to Docker Model Runner via models/load endpoint
type modelRunnerTarget struct {
client *desktop.Client
tag name.Tag
}

func newModelRunnerTarget(client *desktop.Client, tag string) (*modelRunnerTarget, error) {
target := &modelRunnerTarget{
client: client,
}
if tag != "" {
var err error
target.tag, err = name.NewTag(tag)
if err != nil {
return nil, fmt.Errorf("invalid tag: %w", err)
}
}
return target, nil
}

func (t *modelRunnerTarget) Write(ctx context.Context, mdl types.ModelArtifact, progressWriter io.Writer) error {
pr, pw := io.Pipe()
errCh := make(chan error, 1)
go func() {
defer pw.Close()
target, err := tarball.NewTarget(pw)
if err != nil {
errCh <- err
return
}
errCh <- target.Write(ctx, mdl, progressWriter)
}()

loadErr := t.client.LoadModel(ctx, pr)
writeErr := <-errCh

if loadErr != nil {
return fmt.Errorf("loading model archive: %w", loadErr)
}
if writeErr != nil {
return fmt.Errorf("writing model archive: %w", writeErr)
}
id, err := mdl.ID()
if err != nil {
return fmt.Errorf("get model ID: %w", err)
}
if t.tag.String() != "" {
if err := desktopClient.Tag(id, parseRepo(t.tag), t.tag.TagStr()); err != nil {
return fmt.Errorf("tag model: %w", err)
}
}
cmd.PrintErrln("Model pushed successfully")
return nil
}
23 changes: 23 additions & 0 deletions desktop/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package desktop
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"html"
Expand Down Expand Up @@ -704,3 +705,25 @@ func (c *Client) Tag(source, targetRepo, targetTag string) error {

return nil
}

func (c *Client) LoadModel(ctx context.Context, r io.Reader) error {
loadPath := fmt.Sprintf("%s/load", inference.ModelsPrefix)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.modelRunner.URL(loadPath), r)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/x-tar")
req.Header.Set("User-Agent", "docker-model-cli/"+Version)

resp, err := c.modelRunner.Client().Do(req)
if err != nil {
return c.handleQueryError(err, loadPath)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return fmt.Errorf("load failed with status %s: %s", resp.Status, string(body))
}
return nil
}
9 changes: 5 additions & 4 deletions docs/reference/docker_model_package.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
command: docker model package
short: |
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
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
long: |
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
usage: docker model package --gguf <path> [--license <path>...] [--context-size <tokens>] --push TARGET
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
usage: docker model package --gguf <path> [--license <path>...] [--context-size <tokens>] [--push] MODEL
pname: docker model
plink: docker_model.yaml
options:
Expand Down Expand Up @@ -40,7 +40,8 @@ options:
- option: push
value_type: bool
default_value: "false"
description: push to registry (required)
description: |
push to registry (if not set, the model is loaded into the Model Runner content store.
deprecated: false
hidden: false
experimental: false
Expand Down
36 changes: 18 additions & 18 deletions docs/reference/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ Docker Model Runner (EXPERIMENTAL)

### Subcommands

| Name | Description |
|:------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| [`df`](model_df.md) | Show Docker Model Runner disk usage |
| [`inspect`](model_inspect.md) | Display detailed information on one model |
| [`install-runner`](model_install-runner.md) | Install Docker Model Runner (Docker Engine only) |
| [`list`](model_list.md) | List the models pulled to your local environment |
| [`logs`](model_logs.md) | Fetch the Docker Model Runner logs |
| [`package`](model_package.md) | Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry |
| [`ps`](model_ps.md) | List running models |
| [`pull`](model_pull.md) | Pull a model from Docker Hub or HuggingFace to your local environment |
| [`push`](model_push.md) | Push a model to Docker Hub |
| [`rm`](model_rm.md) | Remove local models downloaded from Docker Hub |
| [`run`](model_run.md) | Run a model and interact with it using a submitted prompt or chat mode |
| [`status`](model_status.md) | Check if the Docker Model Runner is running |
| [`tag`](model_tag.md) | Tag a model |
| [`uninstall-runner`](model_uninstall-runner.md) | Uninstall Docker Model Runner |
| [`unload`](model_unload.md) | Unload running models |
| [`version`](model_version.md) | Show the Docker Model Runner version |
| Name | Description |
|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------|
| [`df`](model_df.md) | Show Docker Model Runner disk usage |
| [`inspect`](model_inspect.md) | Display detailed information on one model |
| [`install-runner`](model_install-runner.md) | Install Docker Model Runner (Docker Engine only) |
| [`list`](model_list.md) | List the models pulled to your local environment |
| [`logs`](model_logs.md) | Fetch the Docker Model Runner logs |
| [`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 |
| [`ps`](model_ps.md) | List running models |
| [`pull`](model_pull.md) | Pull a model from Docker Hub or HuggingFace to your local environment |
| [`push`](model_push.md) | Push a model to Docker Hub |
| [`rm`](model_rm.md) | Remove local models downloaded from Docker Hub |
| [`run`](model_run.md) | Run a model and interact with it using a submitted prompt or chat mode |
| [`status`](model_status.md) | Check if the Docker Model Runner is running |
| [`tag`](model_tag.md) | Tag a model |
| [`uninstall-runner`](model_uninstall-runner.md) | Uninstall Docker Model Runner |
| [`unload`](model_unload.md) | Unload running models |
| [`version`](model_version.md) | Show the Docker Model Runner version |



Expand Down
14 changes: 7 additions & 7 deletions docs/reference/model_package.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# docker model package

<!---MARKER_GEN_START-->
Package a GGUF file into a Docker model OCI artifact, with optional licenses, and pushes it to the specified registry
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

### Options

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


<!---MARKER_GEN_END-->
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ require (
github.com/docker/docker v28.2.2+incompatible
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807
github.com/google/go-containerregistry v0.20.6
github.com/mattn/go-isatty v0.0.17
github.com/nxadm/tail v1.4.8
Expand Down Expand Up @@ -43,6 +43,8 @@ require (
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/elastic/go-sysinfo v1.15.3 // indirect
github.com/elastic/go-windows v1.0.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
Expand Down Expand Up @@ -77,6 +79,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand Down
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,15 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e h1:qBkjP4A20f3RXvtstitIPiStQ4p+bK8xcjosrXLBQZ0=
github.com/docker/model-distribution v0.0.0-20250710123110-a633223e127e/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e h1:oafd84kAFBgv/DAYgtXGLkC1KmRpDN+7G3be5+2+hA0=
github.com/docker/model-runner v0.0.0-20250711130825-8907b3ddf82e/go.mod h1:QmSoUNAbqolMY1Aq9DaC+sR/M/OPga0oCT/DBA1z9ow=
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c h1:w9MekYamXmWLe9ZWXWgNXJ7BLDDemXwB8WcF7wzHF5Q=
github.com/docker/model-distribution v0.0.0-20250724114133-a11d745e582c/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807 h1:02vImD8wqUDv6VJ2cBLbqzbjn17IMYEi4ileCEjXMQ8=
github.com/docker/model-runner v0.0.0-20250724122432-ecfa5e7e6807/go.mod h1:rCzRjRXJ42E8JVIA69E9hErJVV5mnUpWdJ2POsktfRs=
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/elastic/go-sysinfo v1.15.3 h1:W+RnmhKFkqPTCRoFq2VCTmsT4p/fwpo+3gKNQsn1XU0=
github.com/elastic/go-sysinfo v1.15.3/go.mod h1:K/cNrqYTDrSoMh2oDkYEMS2+a72GRxMvNP+GC+vRIlo=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
Expand Down
Loading