GoogleContainerTools/skaffold · error

cannot call Run with empty container config

Error message

cannot call Run with empty container config

What it means

localDaemon.Run creates a container from an image reference and returns wait channels plus the container ID. It rejects the call up front with this error when opts.ContainerConfig is nil, because a container cannot be created without a config. It is a defensive programming-contract error, not a daemon failure.

Source

Thrown at pkg/skaffold/docker/image.go:231

// Delete stops, removes, and prunes a running container
func (l *localDaemon) Delete(ctx context.Context, out io.Writer, id string) error {
	if _, err := l.apiClient.ContainerStop(ctx, id, client.ContainerStopOptions{}); err != nil {
		log.Entry(ctx).Debugf("unable to stop running container: %s", err.Error())
	}
	if _, err := l.apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{}); err != nil {
		log.Entry(ctx).Warnf("unable to remove container: %s", err.Error())
	}
	_, err := l.apiClient.ContainerPrune(ctx, client.ContainerPruneOptions{})
	if err != nil {
		return fmt.Errorf("pruning removed container: %w", err)
	}
	return nil
}

// Run creates a container from a given image reference, and returns a wait channel and the container ID.
func (l *localDaemon) Run(ctx context.Context, out io.Writer, opts ContainerCreateOpts) (<-chan container.WaitResponse, <-chan error, string, error) {
	if opts.ContainerConfig == nil {
		return nil, nil, "", fmt.Errorf("cannot call Run with empty container config")
	}
	c, err := l.apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
		Config: opts.ContainerConfig,
		HostConfig: &container.HostConfig{
			NetworkMode:  container.NetworkMode(opts.Network),
			VolumesFrom:  opts.VolumesFrom,
			PortBindings: opts.Bindings,
			Mounts:       opts.Mounts,
		},
		Name: opts.Name,
	})
	if err != nil {
		return nil, nil, "", err
	}
	if _, err := l.apiClient.ContainerStart(ctx, c.ID, client.ContainerStartOptions{}); err != nil {
		return nil, nil, "", err
	}
	if opts.Wait {

View on GitHub (pinned to a1189de023)

Solutions

  1. Set opts.ContainerConfig to a non-nil *container.Config that includes at least the Image field before calling Run.
  2. If options are built conditionally, ensure the default/config-loaded path always assigns ContainerConfig.
  3. If you only wanted an image (not a container), use the image API (e.g. Tag/Push or ImageID) instead of Run.
  4. Update callers broken by an API change so they pass the required config struct.

Example fix

// before
cw, ch, id, err := daemon.Run(ctx, out, docker.ContainerCreateOpts{Image: "alpine"})

// after
cw, ch, id, err := daemon.Run(ctx, out, docker.ContainerCreateOpts{
    ContainerConfig: &container.Config{Image: "alpine"},
})
Defensive patterns

Strategy: validation

Validate before calling

// Go: guard before calling Run
if opts.ContainerConfig == nil || opts.ContainerConfig.Image == "" {
    return fmt.Errorf("ContainerConfig (with Image) must be set before Run")
}

Type guard

// Go: narrowing helper
func hasContainerConfig(opts docker.ContainerCreateOpts) (*container.Config, bool) {
    if opts.ContainerConfig == nil || opts.ContainerConfig.Image == "" {
        return nil, false
    }
    return opts.ContainerConfig, true
}

Try / catch

cw, ch, id, err := daemon.Run(ctx, out, opts)
if err != nil {
    if strings.Contains(err.Error(), "empty container config") {
        return fmt.Errorf("programmer error: initialize ContainerCreateOpts.ContainerConfig (&container.Config{Image: ...}) before Run")
    }
    return err
}

Prevention

When it happens

Trigger: Calling Run(ctx, out, ContainerCreateOpts{...}) with the zero value or with ContainerConfig left unset — e.g. constructing ContainerCreateOpts with only Image/Network/VolumesFrom and forgetting &container.Config{Image: ...}.

Common situations: Plugin/custom-builder code calling the LocalDaemon.Run API after partially populating ContainerCreateOpts; refactors that changed ContainerConfig from a value to a pointer and left callers passing nil; dynamic config assembly where an error path skipped config initialization.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/56af2c14f120aa9a. Report an issue: GitHub.