microsoft/aspire · error · InvalidOperationException

Gateway ' ' is in Kubernetes environment ' ' but issuer ' '…

Error message

Gateway '{builder.Resource.Name}' is in Kubernetes environment '{gatewayEnvironment.Name}' but issuer '{issuer.Resource.Name}' belongs to cert-manager installation '{issuer.Resource.Parent.Name}' in environment '{issuerEnvironment.Name}'. cert-manager is per-cluster, so an issuer can only be used by gateways in the same Kubernetes environment.

What it means

WithTls validates that the cert-manager ClusterIssuer being referenced lives in the same Kubernetes environment (cluster) as the Gateway resource. cert-manager issuers are cluster-scoped, so a gateway in one environment cannot use an issuer installed by a cert-manager resource in a different environment; Aspire throws InvalidOperationException when the parent environments differ (compared with ResourceNameComparer).

Solutions

  1. Create a cert-manager instance in the same Kubernetes environment as the gateway and use its issuer.
  2. Move the WithTls call to use an issuer whose AddCertManager parent matches the gateway's environment.
  3. If the issuer should be shared, restructure so both gateways are in one environment.

Example fix

// before
var envA = builder.AddKubernetesEnvironment("env-a");
var envB = builder.AddKubernetesEnvironment("env-b");
var cm = envA.AddCertManager("cm");
envB.AddGateway("gw").WithTls(cm.AddClusterIssuer("letsencrypt"));

// after
var cmB = envB.AddCertManager("cm-b");
envB.AddGateway("gw").WithTls(cmB.AddClusterIssuer("letsencrypt"));
Defensive patterns

Strategy: validation

Validate before calling

bool SameEnvironment(GatewayResource g, CertManagerIssuerResource i) =>
    string.Equals(g.Parent?.Name, i.Resource.Parent?.Parent?.Name, StringComparison.OrdinalIgnoreCase);

Try / catch

try { gateway.WithTls(issuer); }
catch (InvalidOperationException ex) when (ex.Message.Contains("same Kubernetes environment")) { /* reconfigure issuer in gateway's environment */ }

Prevention

When it happens

Trigger: Calling gateway.WithTls(issuer) where issuer was created by an AddCertManager call on a different Kubernetes environment resource than the one containing the gateway.

Common situations: Multi-environment app models where a shared cert-manager was defined in environment A and gateways were defined in environment B; refactoring that moved a gateway to a new environment without moving the issuer reference.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Kubernetes/CertManagerExtensions.cs:343

    /// but type-safe and refactor-friendly. Throws if the gateway and the issuer's
    /// cert-manager installation are not part of the same Kubernetes environment, since
    /// cert-manager is per-cluster and would otherwise silently produce an unsatisfiable
    /// TLS configuration.
    /// </remarks>
    [AspireExport("withGatewayTlsIssuer")]
    public static IResourceBuilder<KubernetesGatewayResource> WithTls(
        this IResourceBuilder<KubernetesGatewayResource> builder,
        IResourceBuilder<CertManagerIssuerResource> issuer)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(issuer);

        var gatewayEnvironment = builder.Resource.Parent;
        var issuerEnvironment = issuer.Resource.Parent.Parent;
        var nameComparer = new ResourceNameComparer();
        if (!nameComparer.Equals(gatewayEnvironment, issuerEnvironment))
        {
            throw new InvalidOperationException(
                $"Gateway '{builder.Resource.Name}' is in Kubernetes environment '{gatewayEnvironment.Name}' but issuer " +
                $"'{issuer.Resource.Name}' belongs to cert-manager installation '{issuer.Resource.Parent.Name}' in environment " +
                $"'{issuerEnvironment.Name}'. cert-manager is per-cluster, so an issuer can only be used by gateways in the " +
                "same Kubernetes environment.");
        }

        return builder
            .WithTls()
            .WithGatewayAnnotation(ClusterIssuerAnnotationKey, issuer.Resource.Name);
    }

    private static Task<IEnumerable<PipelineStep>> BuildIssuerApplySteps(
        CertManagerResource certManager,
        string chartName)
    {
        var steps = new List<PipelineStep>();

        foreach (var issuer in certManager.Issuers)

View on GitHub (pinned to 25830f84bd)