docker/compose · error

copying between services is not supported

Error message

copying between services is not supported

What it means

The Compose copy API (docker compose cp) only supports copying between the host and containers of one service, in one direction. The source and destination arguments are parsed as SERVICE:PATH; when BOTH sides carry a service prefix, the direction bitmask becomes fromService|toService (acrossServices) and the copy is rejected before any container is contacted. This is an intentional limitation: Compose has no primitive to stream an archive directly between two service containers.

Source

Thrown at pkg/compose/cp.go:70

	projectName = strings.ToLower(projectName)
	srcService, srcPath := splitCpArg(options.Source)
	destService, dstPath := splitCpArg(options.Destination)

	var direction copyDirection
	var serviceName string
	var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
	if srcService != "" {
		direction |= fromService
		serviceName = srcService
		copyFunc = s.copyFromContainer
	}
	if destService != "" {
		direction |= toService
		serviceName = destService
		copyFunc = s.copyToContainer
	}
	if direction == acrossServices {
		return errors.New("copying between services is not supported")
	}

	if direction == 0 {
		return errors.New("unknown copy direction")
	}

	containers, err := s.listContainersTargetedForCopy(ctx, projectName, options, direction, serviceName)
	if err != nil {
		return err
	}

	g := errgroup.Group{}
	for _, cont := range containers {
		ctr := cont
		g.Go(func() error {
			name := getCanonicalContainerName(ctr)
			var msg string
			if direction == fromService {

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Copy in two steps: `docker compose cp svc_a:/path ./tmpfile` then `docker compose cp ./tmpfile svc_b:/path`
  2. If both services can share the file, mount the same volume or bind directory into both services and write to it instead of copying
  3. Use `docker compose exec svc_a sh -c 'tar c ...' | docker compose exec -T svc_b tar x ...` for streaming pipelines in scripts
  4. If you called the Go API, run two Copy calls (one fromService, one toService) with an intermediate host path

Example fix

# before
docker compose cp db:/dump.sql app:/dump.sql
# after
docker compose cp db:/dump.sql ./dump.sql && docker compose cp ./dump.sql app:/dump.sql
Defensive patterns

Strategy: validation

Validate before calling

# before calling compose cp, ensure exactly one side has a service prefix
cp_args_check() {
  case "$1" in *:*) src_svc=1;; *) src_svc=0;; esac
  case "$2" in *:*) dst_svc=1;; *) dst_svc=0;; esac
  [ $((src_svc + dst_svc)) -eq 1 ] || { echo "need exactly one SERVICE: path" >&2; return 2; }
}

Prevention

When it happens

Trigger: Calling docker compose cp svc_a:/path svc_b:/path (or api.ComposeService.Copy with Source='svc_a:/file' and Destination='svc_b:/file'), i.e. both splitCpArg results yield a non-empty service name.

Common situations: Trying to move files between two services in one command (e.g. dumping a database from the db service into the app service), scripting backups where source and target are both containers, or assuming compose cp mirrors docker cp semantics (docker cp also cannot container->container, so users coming from scp expect it to work).

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/fcd06d8729370c2d. Report an issue: GitHub.