crowdsecurity/crowdsec · error

unable to read logs from container %s: %w

Error message

unable to read logs from container %s: %w

What it means

tailContainerAttempt opens the container log stream with d.Client.ContainerLogs(ctx, container.ID, *container.logOptions). If that API call fails (daemon unreachable, container gone/removed, API rejected options), it wraps the error as "unable to read logs from container %s". It is the initial connection (or reconnection attempt, under backoff) to a container's logs failing.

Source

Thrown at pkg/acquisition/modules/docker/run.go:626

		}

		wait := bo.NextBackOff()

		container.logger.Debugf("tail failed but container is (presumed) healthy: %v, retrying in %s", err, wait)

		select {
		case <-time.After(wait):
		case <-container.t.Dying():
			container.logger.Infof("tail stopped")
			return nil
		}
	}
}

func (d *Source) tailContainerAttempt(ctx context.Context, container *ContainerConfig, outChan chan pipeline.Event, bo backoff.BackOff) error {
	dockerReader, err := d.Client.ContainerLogs(ctx, container.ID, *container.logOptions)
	if err != nil {
		return fmt.Errorf("unable to read logs from container %s: %w", container.Name, err)
	}

	// Log connection (both initial and reconnections)
	container.logger.Info("connected to container logs")

	// reset backoff so for the next disconnect, the interval doesn't start from 30sec
	bo.Reset()

	var scanner *bufio.Scanner
	// we use this library to normalize docker API logs (cf. https://ahmet.im/blog/docker-logs-api-binary-format-explained/)
	if container.Tty {
		scanner = bufio.NewScanner(dockerReader)
	} else {
		reader := dlog.NewReader(dockerReader)
		scanner = bufio.NewScanner(reader)
	}

	readerChan := make(chan string)

View on GitHub (pinned to 909b515798)

Solutions

  1. Confirm the container exists and its log driver supports reading: `docker inspect -f '{{.HostConfig.LogConfig}}' <name>` (avoid "none").
  2. Check the docker daemon/socket is reachable and permissions allow the logs API.
  3. With docker-socket-proxy, allow the containers/logs endpoints.
  4. This error is retried via the backoff passed to tailContainerAttempt — leave the source running and it will reconnect when the container/daemon returns.

Example fix

// before (daemon-compose service)
logging:
  driver: none
// after
logging:
  driver: json-file
  options:
    max-size: "10m"
Defensive patterns

Strategy: retry

Validate before calling

// Go: verify the container still exists before tailing
_, err := cli.ContainerInspect(ctx, container.ID)
if err != nil {
	// container gone; re-resolve by name instead of tailing a stale ID
	return reResolve(ctx, container.Name)
}

Try / catch

err := d.tailContainerAttempt(ctx, container, outChan, bo)
if err != nil && strings.Contains(err.Error(), "unable to read logs from container") {
	d.WaitFor(time.After(bo.NextBackOff())) // backoff retry
}

Prevention

When it happens

Trigger: ContainerLogs called with a container ID that no longer exists (container removed between discovery and tail), docker daemon temporarily down, or a socket-proxy denying the /containers/{id}/logs endpoint; also bad log options (e.g. invalid tail/since values rejected by the daemon).

Common situations: Short-lived containers that exit and are removed while crowdsec is starting; docker daemon restart; log driver not supporting reading logs (e.g. "none"); socket-proxy blocking the logs route.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/3e0ba54a91b928b4. Report an issue: GitHub.