Skip to content

Cross-environment EndpointReferences throw "Container app context not found" (PR #16208 fixed this for first-party App Service ↔ ACA but was closed unmerged) #17566

@KennethHoff

Description

@KennethHoff

Is there an existing issue for this?

  • 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:

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:

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:

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) only knows about resources its own PrepareDeploymentTargetsAsync claimed (i.e. resources whose GetComputeEnvironment() == this ACA env). For any cross-env target, the lookup throws:

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<JavaScriptAppResource> 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:

var portal = builder.AddViteApp("portal", "...").WithPnpm().PublishAsAzureStaticWebApp();

var api = builder.AddProject<Projects.Api>("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):

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<string>
            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:

var builder = DistributedApplication.CreateBuilder(args);

var aca = builder.AddAzureContainerAppEnvironment("aca-env");
var appService = builder.AddAzureAppServiceEnvironment("app-service-env");

var web = builder.AddProject<Projects.Web>("web")
    .WithComputeEnvironment(appService);

builder.AddProject<Projects.Api>("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 <web>.

System.InvalidOperationException: Container app context not found for resource <web>.
   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:

public static BicepOutputReference GetHostname<T>(this IResourceBuilder<T> builder)
    where T : JavaScriptAppResource
{
    var target = builder.Resource.Annotations
        .OfType<AzureStaticWebAppDeploymentTargetAnnotation>()
        .LastOrDefault()
        ?? throw new InvalidOperationException(...);

    return target.Module.DefaultHostname;
}

User code becomes:

api.WithEnvironment("Cors__AllowedOrigins__0", portal.GetHostname());

BicepOutputReference is handled correctly by BaseContainerAppContext.ProcessValue at line 254 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplicationtriage:bot-seenAspire triage bot has seen this issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions