Cross-environment EndpointReferences throw "Container app context not found" (PR #16208 fixed this for first-party App Service ↔ ACA but was closed unmerged)
- 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
### Describe the bug
When an ACA-bound resource has an `EndpointReference` (e.g. via `WithEnvironment(name, otherResource.GetEndpoint("http"))`) pointing to a resource that ACA's own `PrepareDeploymentTargetsAsync` didn't claim into `_containerApps`, ACA's bicep-emission processor unconditionally asks ACA's own internal context for that resource's container-app context — and throws because it's not there.
A resource ends up unclaimed for several distinct reasons:
- **Filtered out of `model.GetComputeResources()`** because `IsContainer() && !IsEmulator() && r is not ProjectResource` (e.g. an `ExecutableResource` swapped to a private nested `ExecutableContainerResource` whose `ContainerImageAnnotation` was subsequently stripped). This is the case I directly verified while building a downstream SWA integration.
- **Foreign compute env.** If `WithComputeEnvironment(otherEnv)` was called, ACA's `PrepareDeploymentTargetsAsync` skips the resource (`if (resourceComputeEnvironment != this) continue;` at [`AzureContainerAppEnvironmentResource.cs:202-205`](https://github.com/microsoft/aspire/blob/v13.3.5/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs#L202-L205)). I did not verify this case directly, but PR #16208 was opened specifically to handle App Service ↔ ACA cross-env references and added tests proving it failed before the PR's fix.
- **`ExcludeFromManifest`.** Reported separately as #13927 — same error message, same root cause path (`IsExcludedFromPublish() → GetComputeResources` skips → not in `_containerApps`).
All three paths converge on `ContainerAppEnvironmentContext.GetContainerAppContext(IResource)` throwing because the lookup key isn't in `_containerApps`. A single fix in `BaseContainerAppContext.ProcessValue` would unblock all three.
The relevant code in [`BaseContainerAppContext.cs:222-237`](https://github.com/microsoft/aspire/blob/v13.3.5/src/Aspire.Hosting.Azure.AppContainers/BaseContainerAppContext.cs):
```csharp
if (value is EndpointReference ep)
{
var context = ep.Resource == resource
? this
: _containerAppEnvironmentContext.GetContainerAppContext(ep.Resource);
var mapping = context._endpointMapping[ep.EndpointName];
var url = GetEndpointValue(mapping, EndpointProperty.Url);
return (url, secretType);
}
```
And the symmetric `EndpointReferenceExpression` path at [lines 275-285](https://github.com/microsoft/aspire/blob/v13.3.5/src/Aspire.Hosting.Azure.AppContainers/BaseContainerAppContext.cs#L275-L285):
```csharp
if (value is EndpointReferenceExpression epExpr)
{
var context = epExpr.Endpoint.Resource == resource
? this
: _containerAppEnvironmentContext.GetContainerAppContext(epExpr.Endpoint.Resource);
var mapping = context._endpointMapping[epExpr.Endpoint.EndpointName];
var val = GetEndpointValue(mapping, epExpr.Property);
return (val, secretType);
}
```
`_containerAppEnvironmentContext.GetContainerAppContext(IResource)` ([`ContainerAppEnvironmentContext.cs:64-72`](https://github.com/microsoft/aspire/blob/v13.3.5/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs#L64-L72)) only knows about resources its own `PrepareDeploymentTargetsAsync` claimed (i.e. resources whose `GetComputeEnvironment()` == this ACA env). For any cross-env target, the lookup throws:
```csharp
public BaseContainerAppContext GetContainerAppContext(IResource resource)
{
if (!_containerApps.TryGetValue(resource, out var context))
{
throw new InvalidOperationException($"Container app context not found for resource {resource.Name}.");
}
return context;
}
```
The contract `IComputeEnvironmentResource.GetEndpointPropertyExpression(EndpointReferenceExpression)` was specifically designed for this case — endpoint resolution dispatched through the OWNING env. ACA's processor never consults it for cross-env endpoints; it assumes everything is ACA-local.
### Prior art
@eerhardt opened **#16208 "Add cross-environment reference support for App Service and Container Apps"** which addressed this exact scenario for the first-party App Service ↔ Container Apps direction by adding `TryGetContainerAppContext` / `TryGetAppServiceContext` fallthrough paths in both contexts. It was **closed without merging on 2026-04-16** and I couldn't find a follow-up PR.
That PR fixed the symptom for one specific Aspire-internal cross-env pairing. This issue requests a more general fix: dispatch through `IComputeEnvironmentResource.GetEndpointPropertyExpression(...)` so the resolution works for ANY compute-env integration (including third-party ones built on the public `IComputeEnvironmentResource` contract) rather than enumerating Aspire-internal cross-env pairs in code.
Related: #13927 manifests the same `Container app context not found` error message but from a different root cause (`ExcludeFromManifest`-flagged resources referenced via `WithReference`).
### How I encountered this
I'm experimenting with custom deployment integrations and currently working on one for Azure Static Web Apps — `PublishAsAzureStaticWebApp(...)` on `IResourceBuilder` that ships Vite apps via `@azure/static-web-apps-cli`.
The natural user code for wiring CORS on an ACA-bound .NET backend that should accept calls from SWA-hosted frontends is:
```csharp
var portal = builder.AddViteApp("portal", "...").WithPnpm().PublishAsAzureStaticWebApp();
var api = builder.AddProject("api")
.WithComputeEnvironment(aca)
.WithEnvironment("Cors__AllowedOrigins__0", portal.GetEndpoint("http"));
```
Running `aspire deploy` against this AppHost fails during ACA's bicep emission:
```
[ERR] Container app context not found for resource portal.
```
`portal` is a top-level Azure Bicep resource (`Microsoft.Web/staticSites`) with a `defaultHostname` output, perfectly addressable via cross-module references. ACA's `BaseContainerAppContext.ProcessValue` never tries — it goes straight to its internal `_containerApps` dictionary, which only contains ACA-owned resources, and throws when `portal` isn't there.
PR #16208 added a `TryGetContainerAppContext` / `TryGetAppServiceContext` fallthrough path that handled exactly this for the first-party App Service ↔ ACA pairing — fell back to manifest expressions when no local context existed. This issue requests the same fallthrough be generalised so third-party compute-env integrations (e.g. via the public `IComputeEnvironmentResource` contract) also participate, not just the first-party pair.
### Expected Behavior
When `BaseContainerAppContext` encounters an `EndpointReference` (or `EndpointReferenceExpression`) targeting a resource owned by a different compute environment, it should dispatch endpoint resolution through that env's `IComputeEnvironmentResource.GetEndpointPropertyExpression(...)` instead of looking up its own `_containerApps` dictionary.
Sketch of the fix in `BaseContainerAppContext.ProcessValue` (lines 222-237):
```csharp
if (value is EndpointReference ep)
{
if (ep.Resource != resource)
{
var foreignEnv = ep.Resource.GetComputeEnvironment();
if (foreignEnv is not null && foreignEnv != _containerAppEnvironmentContext.Environment)
{
// Dispatch to the owning env's expression provider.
var expr = foreignEnv.GetEndpointPropertyExpression(
new EndpointReferenceExpression(ep, EndpointProperty.Url));
// ... thread `expr` through AllocateParameter / etc. as a BicepValue
return (..., secretType);
}
}
// existing ACA-local path
...
}
```
Symmetric change for the `EndpointReferenceExpression` branch.
This would let any compute-env integration that correctly implements `IComputeEnvironmentResource.GetEndpointPropertyExpression` participate in cross-env endpoint resolution without users needing custom workarounds. PR #16208 was already 80% of the way there with `TryGetContainerAppContext` / `TryGetAppServiceContext`; generalising to the `IComputeEnvironmentResource` dispatch path makes it work for arbitrary third-party envs too.
### Steps To Reproduce
The minimal repro uses only first-party Aspire APIs (no custom integrations needed) and matches the exact scenarios PR #16208 added tests for:
```csharp
var builder = DistributedApplication.CreateBuilder(args);
var aca = builder.AddAzureContainerAppEnvironment("aca-env");
var appService = builder.AddAzureAppServiceEnvironment("app-service-env");
var web = builder.AddProject("web")
.WithComputeEnvironment(appService);
builder.AddProject("api")
.WithComputeEnvironment(aca)
.WithEnvironment("WEB_URL", web.GetEndpoint("http")); // cross-env EndpointReference
builder.Build().Run();
```
`aspire deploy` (or `aspire publish`) against this AppHost throws:
```
[ERR] Container app context not found for resource web.
```
The same shape with `web` on ACA and `api` on App Service produces the symmetric failure during App Service's bicep emission.
PR #16208's test fixtures cover both directions and were passing in that PR's branch — but the PR was closed unmerged, so the bug still ships in 13.3.5.
### Exceptions (if any)
```
[ERR] Container app context not found for resource .
System.InvalidOperationException: Container app context not found for resource .
at Aspire.Hosting.Azure.AppContainers.ContainerAppEnvironmentContext.GetContainerAppContext(IResource resource)
in /_/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs:line 68
at Aspire.Hosting.Azure.AppContainers.BaseContainerAppContext.ProcessValue(Object value, SecretType secretType, Object parent)
in /_/src/Aspire.Hosting.Azure.AppContainers/BaseContainerAppContext.cs:line 227
...
```
### Aspire doctor output
```
Aspire Environment Check
========================
.NET SDK
✅ .NET 10.0.300 installed (x64)
Container Runtime
✅ Podman v5.8.2: running (auto-detected (only runtime running)) ← active
Environment
⚠️ HTTPS development certificate is only partially trusted
Set SSL_CERT_DIR in your shell profile: export SSL_CERT_DIR="$SSL_CERT_DIR:/home/kennethhoff/.aspnet/dev-certs/trust"
See: https://aka.ms/aspire-prerequisites#dev-certs
Details: The certificate is in the trusted store, but SSL_CERT_DIR is not configured to include
'/home/kennethhoff/.aspnet/dev-certs/trust'. Some applications may not trust the certificate.
'aspire run' will configure this automatically.
Summary: 2 passed, 1 warnings, 0 failed
```
(The dev-cert warning is unrelated to this bug — NixOS environment quirk; the certificate is trusted, just at a non-standard path.)
### Anything else?
- Aspire version: 13.3.5
- .NET SDK: 10.0.300 x64
- OS: Linux (NixOS dev shell)
#### Workaround
My integration exposes a `GetHostname()` extension that returns a `BicepOutputReference` (pointing at the SWA module's `defaultHostname` output) instead of an `EndpointReference`:
```csharp
public static BicepOutputReference GetHostname(this IResourceBuilder builder)
where T : JavaScriptAppResource
{
var target = builder.Resource.Annotations
.OfType()
.LastOrDefault()
?? throw new InvalidOperationException(...);
return target.Module.DefaultHostname;
}
```
User code becomes:
```csharp
api.WithEnvironment("Cors__AllowedOrigins__0", portal.GetHostname());
```
`BicepOutputReference` is handled correctly by `BaseContainerAppContext.ProcessValue` at [line 254](https://github.com/microsoft/aspire/blob/v13.3.5/src/Aspire.Hosting.Azure.AppContainers/BaseContainerAppContext.cs#L254) via `AllocateParameter`, so the cross-module wiring works.
This workaround is fine for hostname-as-BicepOutput cases but it forces every cross-env compute integration to:
- Expose a separate `GetHostname()` / `GetUrl()` / `GetPort()` / `GetX()` API for each endpoint property users might reach for, and
- Train users away from the natural `.GetEndpoint("http")` shape they'd use for any same-env resource.
The fix above (dispatching through `IComputeEnvironmentResource.GetEndpointPropertyExpression`) generalises across all compute envs and all endpoint properties without per-integration boilerplate.
#### Related observation
`IComputeEnvironmentResource.GetEndpointPropertyExpression` has a sensible default implementation that composes scheme/host/port/url from `GetHostAddressExpression`. Any compute-env integration that implements `GetHostAddressExpression` correctly (e.g. returning a `BicepOutputReference` to the env's per-resource hostname output) gets cross-env endpoint URL resolution "for free" via the interface — ACA's processor just doesn't take the off-ramp.
Contributor guide
Assessment
This issue has not been assessed yet.