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
- Set opts.ContainerConfig to a non-nil *container.Config that includes at least the Image field before calling Run.
- If options are built conditionally, ensure the default/config-loaded path always assigns ContainerConfig.
- If you only wanted an image (not a container), use the image API (e.g. Tag/Push or ImageID) instead of Run.
- 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
- Always construct ContainerCreateOpts via a constructor that sets ContainerConfig with Image.
- If creating containers from an image reference, assert cfg.Image != nil first.
- Add a unit test asserting Run is never called with zero-value opts in your plugin code.
- Watch for API changes that turn value fields into pointers and update callers.
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
- INIT_DOCKER_NETWORK_INVALID_MODE
- INIT_DOCKER_NETWORK_INVALID_CONTAINER_NAME
- %q running container image %q errored during run with status
- %q running container image %q timed out after : %v
- unable to remove container: %w
AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05).
Data as JSON: /api/errors/56af2c14f120aa9a.
Report an issue: GitHub.