microsoft/aspire · error · InvalidOperationException

Volume ' ' on resource ' ' does not declare a mount path.

Error message

Volume '{VolumeName}' on resource '{context.Resource.Name}' does not declare a mount path.

What it means

VolumeMountBindingAnnotation.ResolvePath computes the path a volume mount is exposed at. In publish mode or for ContainerResource, the MountPath must be declared; if VolumeName is set but MountPath is null, ResolvePath throws InvalidOperationException stating the volume does not declare a mount path. Without a mount path the host cannot know where inside the container/compute the volume should be attached.

Solutions

  1. Specify the mount path when adding the volume, e.g. WithVolume("mydata", "/data") instead of WithVolume("mydata").
  2. For project resources only running locally in run mode, confirm you are not accidentally in publish mode where MountPath is required.
  3. If you own the annotation construction, set the MountPath property on the VolumeMountAnnotation before resolution.

Example fix

// before
var pg = builder.AddPostgres("pg").WithDataVolume("pgdata"); // no mount path

// after
var pg = builder.AddPostgres("pg")
    .WithVolume("pgdata", "/var/lib/postgresql/data");
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every volume annotation has a mount path before publish/container run:
if (volumeAnnotation.MountPath is null && (context.ExecutionContext.IsPublishMode || resource is ContainerResource))
    throw new InvalidOperationException($"Volume '{volumeAnnotation.VolumeName}' needs an explicit mount path.");

Try / catch

try
{
    var path = volumeMountAnnotation.ResolvePath(context);
}
catch (InvalidOperationException ex)
{
    logger.LogError(ex, "Volume mount path missing: {Message}", ex.Message);
}

Prevention

When it happens

Trigger: Calling WithVolume/WithPersistentVolume-style APIs (WithPersistentVolumeCore/WithVolumeCore call ResolvePath) where the volume annotation has a VolumeName but no MountPath, in publish mode or on a container resource.

Common situations: Using WithVolume(name) overload without specifying the container mount path when targeting containers or publish; refactoring from run-mode-only volume usage into containerized/published scenarios where MountPath becomes mandatory; copying volume config from a project resource to a container resource.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/8b9141664dd00579. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting/ApplicationModel/VolumeMountBindingAnnotation.cs:83

    /// <summary>
    /// Resolves the storage path the workload should use for the current execution mode.
    /// </summary>
    /// <param name="context">The environment callback context being evaluated.</param>
    /// <returns>
    /// <see cref="MountPath"/> when publishing or when the workload runs as a container, and otherwise a
    /// local host directory.
    /// </returns>
    /// <exception cref="InvalidOperationException">
    /// Thrown when the deployed mount path is required but this binding does not declare one.
    /// </exception>
    public string ResolvePath(EnvironmentCallbackContext context)
    {
        ArgumentNullException.ThrowIfNull(context);

        if (context.ExecutionContext.IsPublishMode || context.Resource is ContainerResource)
        {
            var mountPath = MountPath ?? throw new InvalidOperationException(
                $"Volume '{VolumeName}' on resource '{context.Resource.Name}' does not declare a mount path.");

            if (context.ExecutionContext.IsPublishMode)
            {
                ThrowIfEnvironmentCannotMount(context);
            }

            return mountPath;
        }

        // Prefer this binding's own resolver. The sibling scan below only exists for the name-match
        // composition, where the mount and the compute environment binding are spelled as two separate
        // calls and the mount-declaring annotation therefore carries no resolver of its own.
        //
        // Scanning unconditionally would alias distinct volumes, because VolumeName is not a unique key —
        // two compute environments can each declare a volume under the same name, and every binding would
        // then select the last resolver and point at one environment's store. Aspire.Hosting.Kubernetes
        // rejects that shape up front, but this annotation is public and shared across compute

View on GitHub (pinned to 25830f84bd)