Volume sharing semantics are inconsistent across run mode and compute environments
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Is your feature request related to a problem? Please describe the problem.
A named volume does not mean the same thing across targets. Given two resources that each declare the same volume name:
```csharp
builder.AddContainer("a", "img").WithVolume("data", "/data");
builder.AddProject("b").WithVolume("data", "/data", env: "DATA_PATH");
```
the sharing behavior differs by target:
| Target | Result | Where |
|---|---|---|
| Run mode — containers | **shared** | literal name flows into `ContainerMountAnnotation.Source` and becomes the Docker named volume |
| Run mode — projects/executables | isolated | `VolumeMountPathResolver.GetLocalPath` scopes by `hash(resource.Name)` |
| Docker Compose | **shared** | `DockerComposeEnvironmentContext` uses `mount.Source` verbatim as the Compose volume name |
| Azure Container Apps | isolated | volume named `$"{prefix}_{normalizedResourceName}_{volumeIndex}"` |
| Kubernetes | isolated | default storage type is `emptyDir`, which is per-pod |
So the same AppHost code shares storage on Compose and in run-mode containers, and isolates it on ACA, Kubernetes, and for run-mode projects and executables.
Note this is not new — run-mode containers have shared by literal name for a long time. `VolumeNameGenerator` exists precisely because raw names are not auto-scoped, so integrations (`WithDataVolume()`) generate `{appname}-{apphostHash}-{resourceName}-{suffix}` to avoid collisions. It only became visible because #19404 added a project/executable path that deliberately isolates.
Discovered during review of #19404 (thanks @JamesNK).
### Describe the solution you'd like
No immediate change. #19404 keeps projects and executables isolated, which is the safe default for the portable volume path convention: it guarantees that resolving a volume path never silently hands two workloads the same directory.
What needs deciding is the model, not the implementation:
**Docker Compose is arguably the odd one out rather than the reference.** Compose almost always schedules on a single node, so "named volume = shared mount" is nearly free. On ACA and Kubernetes, shared volume semantics get considerably more complicated — you are into access modes (`ReadWriteOnce` vs `ReadWriteMany`), storage classes, node affinity, and whether the backing store can even support concurrent writers. Treating a bare name as an implicit sharing contract does not survive that translation.
This likely points at a missing primitive: sharing should be something an AppHost author **declares explicitly**, rather than something inferred from two resources coincidentally passing the same string. A first-class volume resource, referenced by multiple workloads, would let each compute environment translate a stated sharing requirement into whatever its platform actually supports — and fail loudly when it cannot.
The hard part, and the reason this is not solvable today: **there is no story for sharing a volume between an executable/project and a container within the same AppHost.** The container sees a mount inside its own filesystem namespace; the host process sees a local directory. Making those the same storage requires bind-mount plumbing and path translation that does not exist yet, and the semantics would still not survive being published to ACA or Kubernetes.
Rough shape of the work, for discussion:
- Decide whether a bare volume name should imply sharing at all, or whether sharing must be explicit.
- Consider a first-class volume resource that workloads reference, so sharing is declared rather than inferred.
- Define what each compute environment does when asked for sharing it cannot provide — most likely a publish-time error rather than silently degrading to isolated storage.
- Separately work out host-process ↔ container sharing in run mode, which is the blocker for making any of the above consistent in the inner loop.
### Additional context
Relevant code:
- `src/Shared/VolumeMountPathResolver.cs` — `GetLocalPath` scopes by resource name; the comment there already acknowledges that "generic target publishers do not share volume identity consistently".
- `src/Aspire.Hosting.Docker/DockerComposeEnvironmentContext.cs` — `var name = mount.Source;`
- `src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs` — `$"{prefix}_{normalizedResourceName}_{volumeIndex}"`
- `src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs` — `DefaultStorageType = "emptyDir"`
- `src/Aspire.Hosting/VolumeNameGenerator.cs` — the existing per-resource scoping helper used by integrations
Contributor guide
Research direction
Read the volume behavior in src/Shared/VolumeMountPathResolver.cs, src/Aspire.Hosting.Docker/DockerComposeEnvironmentContext.cs, src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs, src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs, and src/Aspire.Hosting/VolumeNameGenerator.cs. Compare how each environment handles shared names and review the discussion around #19404. Done means an agreed sharing model and explicit behavior for unsupported environments, not an implementation yet.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, csharp, docker-compose, kubernetes
- Domain
- cloud, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100