dagger/dagger · error

source path %q is a directory

Error message

source path %q is a directory

What it means

copyFile in util/layercopy refuses to copy when the selected source path is a directory: after selectBase resolves srcPath, src.baseInfo.IsDir() short-circuits with this error. CopyFile is for single-file copies only; directory copies must go through the layer's directory copy path instead.

Source

Thrown at util/layercopy/copy_linux.go:166

		}
	} else if destExists && destInfo.IsDir() {
		destPath = filepath.Join(destPath, filepath.Base(src.baseView))
	}
	return c.copyEntry(ctx, src, matcher, root, destPath, opts, matchState{}, nil, resolvedParent{})
}

func (c *Copier) copyFile(ctx context.Context, src *source, srcPath, destPath string, opts CopyOptions) error {
	select {
	case <-ctx.Done():
		return context.Cause(ctx)
	default:
	}

	if err := src.selectBase(srcPath, true); err != nil {
		return err
	}
	if src.baseInfo.IsDir() {
		return fmt.Errorf("source path %q is a directory", srcPath)
	}
	destPath = cleanContainerPath(destPath)
	if opts.DestPathHintIsDir {
		if _, _, err := c.dest.ensureDir(destPath, nil, CopyOptions{Chown: opts.Chown}, false); err != nil {
			return err
		}
		destPath = filepath.Join(destPath, filepath.Base(cleanContainerPath(srcPath)))
	} else if destInfo, exists, err := c.dest.statView(destPath); err != nil {
		return err
	} else if exists && destInfo.IsDir() {
		destPath = filepath.Join(destPath, filepath.Base(cleanContainerPath(srcPath)))
	}

	ent := sourceEntry{
		Rel:      "",
		ViewPath: src.baseView,
		RealPath: src.baseReal,
		Info:     src.baseInfo,

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Pass a specific file path (including the filename) as the source, not a directory.
  2. Use the directory-copy API/entry point for directories instead of CopyFile.
  3. Verify the path inside the source image is actually a file (`ls` the layer/container path).
  4. If the path is dynamic, stat it first and branch to the directory copy routine when IsDir().

Example fix

// before
err := layercopy.CopyFile(ctx, src, dest, layercopy.CopyOptions{SrcPath: "/app/assets"}) // directory
// after
err := layercopy.CopyDir(ctx, src, dest, layercopy.CopyOptions{SrcPath: "/app/assets"})
Defensive patterns

Strategy: validation

Validate before calling

info, err := src.Stat(srcPath)
if err != nil { return err }
if info.IsDir() {
	return useDirectoryCopy(srcPath)
}

Type guard

func isRegularFile(info os.FileInfo) bool { return info.Mode().IsRegular() }

Try / catch

err := layercopy.CopyFile(ctx, src, dest, opts)
if err != nil && strings.Contains(err.Error(), "is a directory") {
	return layercopy.CopyDir(ctx, src, dest, opts) // or reject with a clear user message
}

Prevention

When it happens

Trigger: Calling util/layercopy.CopyFile with opts.SrcPath (or the container-resolved source) pointing at a directory inside the source layer/image; a config error where a directory was given where a file path was expected.

Common situations: Dockerfile/container COPY-style operations where the user gave a directory as the source but the engine dispatched the single-file copy routine; typos or wrong glob expansion resolving a directory instead of the intended file.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/76da495c05eaa773. Report an issue: GitHub.