containerd/containerd · warning

container is already in starting state

Error message

container is already in starting state

What it means

The CRI start path sets `Starting = true` inside an atomic status update; if a previous StartContainer on the same container already set that flag and is still running, a second start is rejected to prevent two concurrent starts of one container.

Source

Thrown at internal/cri/server/container_start.go:224

	)

	return &runtime.StartContainerResponse{}, nil
}

// setContainerStarting sets the container into starting state. In starting state, the
// container will not be removed or started again.
func setContainerStarting(container containerstore.Container) error {
	return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) {
		// Return error if container is not in created state.
		if status.State() != runtime.ContainerState_CONTAINER_CREATED {
			return status, fmt.Errorf("container is in %s state", criContainerStateToString(status.State()))
		}
		// Do not start the container when there is a removal in progress.
		if status.Removing {
			return status, errors.New("container is in removing state, can't be started")
		}
		if status.Starting {
			return status, errors.New("container is already in starting state")
		}
		status.Starting = true
		return status, nil
	})
}

// resetContainerStarting resets the container starting state on start failure. So
// that we could remove the container later.
func resetContainerStarting(container containerstore.Container) error {
	return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) {
		status.Starting = false
		return status, nil
	})
}

// createContainerLoggers creates container loggers and return write closer for stdout and stderr.
func (c *criService) createContainerLoggers(logPath string, tty bool) (stdout io.WriteCloser, stderr io.WriteCloser, err error) {
	if logPath != "" {

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Wait for the first start to complete (poll ContainerStatus for RUNNING) instead of retrying immediately.
  2. Deduplicate start requests per container ID in the caller.
  3. Increase client timeout so slow starts are not retried while in flight.

Example fix

// before
client.StartContainer(ctx, id) // retry while first start still running
// after
err := client.StartContainer(ctx, id)
if err != nil && strings.Contains(err.Error(), "already in starting state") {
  return nil // first start still in progress; wait instead
}
Defensive patterns

Strategy: try-catch

Type guard

func isAlreadyStarting(err error) bool { return err != nil && strings.Contains(err.Error(), "already in starting state") }

Try / catch

if err := client.StartContainer(ctx, id); err != nil {
  if isAlreadyStarting(err) {
    return nil // first start in progress; poll for RUNNING instead
  }
  return err
}

Prevention

When it happens

Trigger: Two concurrent StartContainer calls for the same container ID; a retried start after a client timeout while the first start is still in flight inside the status update closure in internal/cri/server/container_start.go.

Common situations: kubelet retry loops issuing duplicate starts; a CRI client with aggressive timeouts retrying a slow start; duplicate events from a controller reconciler.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/7bb1177b0b264991. Report an issue: GitHub.