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
- Pass a specific file path (including the filename) as the source, not a directory.
- Use the directory-copy API/entry point for directories instead of CopyFile.
- Verify the path inside the source image is actually a file (`ls` the layer/container path).
- 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
- Validate that the source path is a file before dispatching to CopyFile.
- Route directory sources to the directory copy routine by design, not error.
- Include the filename in config-derived source paths (no bare directory hints).
- Test layer-copy configs against paths that may change type between versions.
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
- failed to chmod %s
- failed to utime %s
- failed to remove existing destination file %s: %w
- failed to stat destination file %s: %w
- failed to resolve source path %s: %w
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/76da495c05eaa773.
Report an issue: GitHub.