jdx/mise · error

cannot copy a file or symlink to the image root

Error message

cannot copy a file or symlink to the image root

What it means

`build_layer_from_path` copies a host file or symlink into the image at the given image path. If the destination trims to the empty string (image root, e.g. image_path "/"), it refuses — a file cannot be placed at the container root without a name. Directories are handled earlier via `build_layer_from_dir`.

Solutions

  1. Set the image path to the full destination file name, e.g. "/etc/foo.conf" instead of "/".
  2. If the intent was to copy directory contents, ensure the host path is a directory (the directory branch handles trailing '/').
  3. Validate copy specs before building: reject empty trimmed targets for file/symlink sources.

Example fix

// before
build_layer_from_path("./app.conf", "/", owner)?; // error

// after
build_layer_from_path("./app.conf", "/etc/app.conf", owner)?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_copy_spec(host: &Path, image_path: &str) -> Result<()> {
    let trimmed = image_path.trim_matches('/');
    if !host.is_dir() && trimmed.is_empty() {
        bail!("file copy spec needs a concrete image path, got {image_path:?}");
    }
    Ok(())
}

Try / catch

match build_layer_from_path(host, image_path, owner) {
    Err(e) if e.to_string().contains("cannot copy a file or symlink to the image root") => {
        bail!("give the file a full destination path like /etc/app.conf")
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling `build_layer_from_path` with a non-directory host path and an image_path of "/", "", or "//" so `trim_matches('/')` yields an empty target.

Common situations: Config mistake where a copy rule intended to place a file into a directory instead targets the root; templated image_path evaluated to "/"; copy spec written as "/" for a single file.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/dfc862a08a85fb34. Report an issue: GitHub.

Appendix: source

Thrown at src/oci/layer.rs:174

}

/// Build a reproducible layer from one host file, symlink, or directory.
/// Directory contents are placed under `image_path`; a file or symlink is
/// placed at `image_path` itself.
pub(crate) fn build_layer_from_path(
    host_path: &Path,
    image_path: &str,
    owner: LayerOwner,
) -> Result<LayerBlob> {
    let metadata = std::fs::symlink_metadata(host_path)
        .wrap_err_with(|| format!("reading metadata for {}", host_path.display()))?;
    let target = image_path.trim_matches('/');

    if metadata.is_dir() {
        return build_layer_from_dir(host_path, target, owner);
    }
    if target.is_empty() {
        eyre::bail!("cannot copy a file or symlink to the image root");
    }
    if image_path.ends_with('/') {
        eyre::bail!(
            "copy image path {image_path:?} ends with `/` but {} is not a directory; \
             specify the exact destination file name",
            host_path.display()
        );
    }

    let kind = if metadata.file_type().is_symlink() {
        EntryKind::Symlink(
            std::fs::read_link(host_path)
                .wrap_err_with(|| format!("reading symlink {}", host_path.display()))?,
        )
    } else if metadata.is_file() {
        EntryKind::File
    } else {
        eyre::bail!("unsupported host path type: {}", host_path.display());

View on GitHub (pinned to 533346cc37)