Join our Discord Server
Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.

Testcontainers Tutorial: Docker Model Runner Guide

7 min read

Docker Model Runner is an experimental feature introduced in Docker Desktop 4.40+ that provides a Docker-native experience for running Large Language Models (LLMs) locally. Unlike traditional containerized approaches, Docker Model Runner runs AI models as host-level processes using optimized inference engines (currently llama.cpp), providing direct GPU acceleration on Apple Silicon Macs.

With the Model Runner feature, Docker provides inference capabilities to developers on their laptop, and in the future in CI, allowing them to run LLM models locally. This is an important feature to help developing GenAI applications. The runner essentially provides GPU-accelerated inference engines that are accessible both through the Docker socket (/var/run/docker.sock) and via a TCP connection at model-runner.docker.internal:80.

Key Architecture Points:

  • Models don’t run in containers – they run directly on the host for optimal performance
  • GPU-accelerated inference – Direct access to Apple’s Metal API on M-series chips
  • Host-installed inference server – Uses llama.cpp natively on your Mac
  • OCI artifact storage – Models are stored as standardized artifacts in Docker Hub

This tutorial focuses on the Testcontainers module, which allows you to programmatically manage Docker Model Runner instances in your Go applications.

Comprehensive Testcontainers Tutorial for Docker Users

Prerequisites

Before starting this tutorial, ensure you have:

  • Go 1.19+ installed on your system
  • Docker installed and running
  • Basic Go knowledge (modules, contexts, error handling)
  • Internet connection for pulling model artifacts

Step 1: Project Setup

1.1 Initialize a New Go Module

mkdir dockermodelrunner-tutorial
cd dockermodelrunner-tutorial
go mod init dockermodelrunner-tutorial
....
go: creating new go.mod: module dockermodelrunner-tutorial




1.2 Add the Required Dependencies

go get github.com/testcontainers/testcontainers-go/modules/dockermodelrunner...


go: downloading github.com/yusufpapurcu/wmi v1.2.4
go: added dario.cat/mergo v1.0.1
go: added github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161
go: added github.com/Microsoft/go-winio v0.6.2
go: added github.com/cenkalti/backoff/v4 v4.2.1
go: added github.com/containerd/log v0.1.0
go: added github.com/containerd/platforms v0.2.1
go: added github.com/cpuguy83/dockercfg v0.3.2
go: added github.com/davecgh/go-spew v1.1.1
go: added github.com/distribution/reference v0.6.0
go: added github.com/docker/docker v28.0.1+incompatible
go: added github.com/docker/go-connections v0.5.0
go: added github.com/docker/go-units v0.5.0
go: added github.com/ebitengine/purego v0.8.2
go: added github.com/felixge/httpsnoop v1.0.4
go: added github.com/go-logr/logr v1.4.2
go: added github.com/go-logr/stdr v1.2.2
go: added github.com/go-ole/go-ole v1.2.6
go: added github.com/gogo/protobuf v1.3.2
go: added github.com/google/uuid v1.6.0
go: added github.com/klauspost/compress v1.17.6
go: added github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0
go: added github.com/magiconair/properties v1.8.10
go: added github.com/moby/docker-image-spec v1.3.1
go: added github.com/moby/patternmatcher v0.6.0
go: added github.com/moby/sys/sequential v0.5.0
go: added github.com/moby/sys/user v0.1.0
go: added github.com/moby/sys/userns v0.1.0
go: added github.com/moby/term v0.5.0
go: added github.com/morikuni/aec v1.0.0
go: added github.com/opencontainers/go-digest v1.0.0
go: added github.com/opencontainers/image-spec v1.1.1
go: added github.com/pkg/errors v0.9.1
go: added github.com/pmezard/go-difflib v1.0.0
go: added github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c
go: added github.com/shirou/gopsutil/v4 v4.25.1
go: added github.com/sirupsen/logrus v1.9.3
go: added github.com/stretchr/testify v1.10.0
go: added github.com/testcontainers/testcontainers-go v0.37.0
go: added github.com/testcontainers/testcontainers-go/modules/dockermodelrunner v0.37.2
go: added github.com/testcontainers/testcontainers-go/modules/socat v0.37.0
go: added github.com/tklauser/go-sysconf v0.3.12
go: added github.com/tklauser/numcpus v0.6.1
go: added github.com/yusufpapurcu/wmi v1.2.4
go: added go.opentelemetry.io/auto/sdk v1.1.0
go: added go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0
go: added go.opentelemetry.io/otel v1.35.0
go: added go.opentelemetry.io/otel/metric v1.35.0
go: added go.opentelemetry.io/otel/trace v1.35.0
go: added golang.org/x/crypto v0.37.0
go: added golang.org/x/sys v0.32.0
go: added gopkg.in/yaml.v3 v3.0.1




Step 2: Basic Container Setup

2.1 Create Your First Docker Model Runner Container

Create a file named main.go:

package main

import (
"context"
"log"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/dockermodelrunner"
)

func main() {
ctx := context.Background()

// Define model details
const (
modelNamespace = "ai"
modelName = "smollm2"
modelTag = "360M-Q4_K_M"
fqModelName = modelNamespace + "/" + modelName + ":" + modelTag
)

// Create and start the Docker Model Runner container
dmrCtr, err := dockermodelrunner.Run(
ctx,
dockermodelrunner.WithModel(fqModelName),
)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}

// Ensure proper cleanup
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

log.Println("Docker Model Runner container started successfully!")

// Keep the container running for demonstration
time.Sleep(10 * time.Second)
}

2.2 Run Your First Example

go run main.go

Step 3: Working with Models at Runtime

3.1 Pulling Models Dynamically

Create model_operations.go:

package main

import (
"context"
"log"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/dockermodelrunner"
)

func pullModelExample() {
ctx := context.Background()

// Start container without pre-loading a model
dmrCtr, err := dockermodelrunner.Run(ctx)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

// Define models to pull
models := []string{
"ai/smollm2:360M-Q4_K_M",
"ai/llama3.2:latest",
}

for _, modelName := range models {
log.Printf("Pulling model: %s", modelName)

// Create a timeout context for the pull operation
pullCtx, cancel := context.WithTimeout(ctx, 60*time.Second)

err = dmrCtr.PullModel(pullCtx, modelName)
cancel() // Always cancel to free resources

if err != nil {
log.Printf("failed to pull model %s: %s", modelName, err)
continue
}

log.Printf("Successfully pulled model: %s", modelName)
}
}

3.2 Inspecting Models

Add this function to your file:

func inspectModelExample() {
ctx := context.Background()

dmrCtr, err := dockermodelrunner.Run(ctx)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

// Pull a model first
modelNamespace := "ai"
modelName := "smollm2"
modelTag := "360M-Q4_K_M"
fqModelName := modelNamespace + "/" + modelName + ":" + modelTag

err = dmrCtr.PullModel(ctx, fqModelName)
if err != nil {
log.Printf("failed to pull model: %s", err)
return
}

// Inspect the model
model, err := dmrCtr.InspectModel(ctx, modelNamespace, modelName+":"+modelTag)
if err != nil {
log.Printf("failed to inspect model: %s", err)
return
}

log.Printf("Model inspection result: %+v", model)
}

3.3 Listing Available Models

Add this function:

func listModelsExample() {
ctx := context.Background()

dmrCtr, err := dockermodelrunner.Run(ctx)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

// Pull some models
models := []string{
"ai/smollm2:360M-Q4_K_M",
"ai/llama3.2:latest",
}

for _, modelName := range models {
err = dmrCtr.PullModel(ctx, modelName)
if err != nil {
log.Printf("failed to pull model %s: %s", modelName, err)
}
}

// List all locally available models
localModels, err := dmrCtr.ListModels(ctx)
if err != nil {
log.Printf("failed to list models: %s", err)
return
}

log.Println("Locally available models:")
for i, model := range localModels {
log.Printf(" %d. %+v", i+1, model)
}
}

Step 4: Advanced Configuration

4.1 Container Customization

Create advanced_config.go:

package main

import (
"context"
"log"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/dockermodelrunner"
"github.com/testcontainers/testcontainers-go/wait"
)

func advancedConfigExample() {
ctx := context.Background()

dmrCtr, err := dockermodelrunner.Run(
ctx,
dockermodelrunner.WithModel("ai/smollm2:360M-Q4_K_M"),
testcontainers.WithEnv(map[string]string{
"CUSTOM_ENV_VAR": "custom_value",
}),
testcontainers.WithExposedPorts("8080/tcp"),
testcontainers.WithWaitStrategy(
wait.ForLog("Model loaded successfully").WithStartupTimeout(60*time.Second),
),
testcontainers.WithLabels(map[string]string{
"app": "dockermodelrunner",
"version": "tutorial",
}),
)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

log.Println("Advanced configured container started successfully!")
}

4.2 Error Handling and Timeouts

Create error_handling.go:

package main

import (
"context"
"errors"
"log"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/dockermodelrunner"
)

func robustModelOperations() {
ctx := context.Background()

// Start container with timeout
startCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

dmrCtr, err := dockermodelrunner.Run(startCtx)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

// Robust model pulling with retries
modelName := "ai/smollm2:360M-Q4_K_M"
maxRetries := 3
retryDelay := 5 * time.Second

for attempt := 1; attempt <= maxRetries; attempt++ {
log.Printf("Attempt %d to pull model: %s", attempt, modelName)

pullCtx, cancel := context.WithTimeout(ctx, 120*time.Second)
err = dmrCtr.PullModel(pullCtx, modelName)
cancel()

if err == nil {
log.Printf("Successfully pulled model on attempt %d", attempt)
break
}

log.Printf("Attempt %d failed: %s", attempt, err)

if attempt < maxRetries {
log.Printf("Waiting %v before retry...", retryDelay)
time.Sleep(retryDelay)
} else {
log.Fatalf("Failed to pull model after %d attempts", maxRetries)
}
}

// Verify model was pulled
models, err := dmrCtr.ListModels(ctx)
if err != nil {
log.Printf("failed to list models: %s", err)
return
}

if len(models) == 0 {
log.Println("No models found locally")
return
}

log.Printf("Found %d models locally", len(models))
}

Step 5: Complete Working Example

Create complete_example.go:

package main

import (
"context"
"fmt"
"log"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/dockermodelrunner"
)

func completeWorkflow() {
ctx := context.Background()

log.Println("🚀 Starting Docker Model Runner Tutorial")

// Step 1: Create container
log.Println("📦 Creating Docker Model Runner container...")
dmrCtr, err := dockermodelrunner.Run(ctx)
if err != nil {
log.Fatalf("❌ Failed to start container: %s", err)
}
defer func() {
log.Println("🧹 Cleaning up container...")
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("⚠️ Failed to terminate container: %s", err)
} else {
log.Println("✅ Container terminated successfully")
}
}()

log.Println("✅ Container started successfully")

// Step 2: Pull multiple models
models := []string{
"ai/smollm2:360M-Q4_K_M",
}

for _, modelName := range models {
log.Printf("⬇️ Pulling model: %s", modelName)

pullCtx, cancel := context.WithTimeout(ctx, 180*time.Second)
err = dmrCtr.PullModel(pullCtx, modelName)
cancel()

if err != nil {
log.Printf("❌ Failed to pull model %s: %s", modelName, err)
continue
}
log.Printf("✅ Successfully pulled: %s", modelName)
}

// Step 3: List all models
log.Println("📋 Listing all available models...")
localModels, err := dmrCtr.ListModels(ctx)
if err != nil {
log.Printf("❌ Failed to list models: %s", err)
return
}

fmt.Printf("📊 Found %d models:\n", len(localModels))
for i, model := range localModels {
fmt.Printf(" %d. %+v\n", i+1, model)
}

// Step 4: Inspect a specific model
if len(localModels) > 0 {
// For inspection, we need namespace and name:tag format
modelNamespace := "ai"
modelNameTag := "smollm2:360M-Q4_K_M"

log.Printf("🔍 Inspecting model: %s/%s", modelNamespace, modelNameTag)
model, err := dmrCtr.InspectModel(ctx, modelNamespace, modelNameTag)
if err != nil {
log.Printf("❌ Failed to inspect model: %s", err)
} else {
log.Printf("✅ Model details: %+v", model)
}
}

log.Println("🎉 Tutorial completed successfully!")
}

func main() {
completeWorkflow()
}

Step 6: Running the Tutorial

6.1 Run the Complete Example

go run complete_example.go

6.2 Expected Output

You should see output similar to:

🚀 Starting Docker Model Runner Tutorial
📦 Creating Docker Model Runner container...
✅ Container started successfully
⬇️  Pulling model: ai/smollm2:360M-Q4_K_M
✅ Successfully pulled: ai/smollm2:360M-Q4_K_M
📋 Listing all available models...
📊 Found 1 models:
   1. {/* model details */}
🔍 Inspecting model: ai/smollm2:360M-Q4_K_M
✅ Model details: {/* inspection results */}
🎉 Tutorial completed successfully!
🧹 Cleaning up container...
✅ Container terminated successfully

Step 7: Best Practices

7.1 Resource Management

// Always use context with timeout for long operations
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()

// Always defer container cleanup
defer func() {
if err := testcontainers.TerminateContainer(dmrCtr); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()

7.2 Error Handling

// Implement retry logic for network operations
func pullWithRetry(dmrCtr *dockermodelrunner.Container, ctx context.Context, modelName string, maxRetries int) error {
for i := 0; i < maxRetries; i++ {
if err := dmrCtr.PullModel(ctx, modelName); err != nil {
if i == maxRetries-1 {
return err
}
time.Sleep(time.Duration(i+1) * 5 * time.Second)
continue
}
return nil
}
return nil
}

7.3 Model Naming Convention

  • Use the format: namespace/name:tag
  • Examples:
    • ai/smollm2:360M-Q4_K_M
    • ai/llama3.2:latest
    • huggingface/bert:base-uncased

Troubleshooting

Common Issues

  1. Container fails to start: Ensure Docker is running and you have sufficient resources
  2. Model pull timeout: Increase timeout duration or check internet connection
  3. Permission errors: Ensure your user has Docker permissions

Debug Tips

// Enable verbose logging
log.SetFlags(log.LstdFlags | log.Lshortfile)

// Add debug prints
log.Printf("Debug: Current context deadline: %v", ctx.Deadline())

Next Steps

Conclusion

You’ve now learned how to:

  • Set up and use the Docker Model Runner with Testcontainers Go
  • Pull, inspect, and list AI models as OCI artifacts
  • Handle errors and timeouts properly
  • Implement best practices for resource management

The Docker Model Runner module makes it easy to work with AI models in your Go applications using the familiar Testcontainers approach.

References

Have Queries? Join https://launchpass.com/collabnix

Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.
Collabnixx
Chatbot
Join our Discord Server
Index