Provides extensions and resource definitions for an Aspire AppHost to publish and deploy applications to a Radius (radapp.io) compute environment.
Install
| Product | Versions |
|---|---|
| .NET |
README
Radius hosting integration
Use this integration to publish and deploy an Aspire AppHost's applications to a Radius compute environment.
AddRadiusEnvironment is an Aspire compute environment, the same kind of building block as
AddKubernetesEnvironment, AddDockerComposeEnvironment, and AddAzureContainerAppEnvironment.
Add it to your AppHost, keep your existing resource graph unchanged, and target it with the standard
aspire publish / aspire deploy lifecycle β Radius becomes just another target you
deploy to, with no changes to how you declare AddContainer, AddProject, AddRedis, and friends.
Radius participates only at publish/deploy time; aspire run continues to run your app locally as usual.
Preview / prototype. This integration is an early prototype. The public API surface and the generated Bicep contract may change in future versions. Pin the integration version in
AppHost.csprojand avoid taking dependencies on any internal types.
This README is layered by intent:
- Getting started β the happy path: add the environment, run, publish, deploy.
- Deploying to a cloud β Azure/AWS providers and credentials.
- Production & platform features β secret stores, multiple resource groups, resource/recipe customization.
- Reference β supported resources, diagnostics, and known limitations.
Getting started
Prerequisites
- Radius v0.60.0 or later; v0.60.2 recommended. The integration's E2E installation pins target Radius v0.60.2. The minimum supported control-plane version remains v0.60; the generated Bicep (resource types,
secretStores, andrecipeConfig) targets the schemas shipped in v0.60.0. Radius v0.59 and earlier are not supported, and the incompatibility is silent: v0.60 renamed theRadius.Core/recipePacksfieldsrecipeKind/recipeLocationtokind/source, and the Radius API server drops fields it does not recognize instead of rejecting them. Deploying these artifacts to a v0.59 cluster therefore produces an empty recipe pack with no error, and every backing resource then fails to resolve a recipe. Because that failure is silent,aspire deploychecks the control plane version before deploying and fails withASPIRERADIUS091rather than producing a non-functional app. The check readsrad versionagainst the cluster your activeradworkspace targets (setASPIRE_RADIUS_KUBE_CONTEXTto override), so it is not affected by which contextkubectlcurrently points at; a version it cannot read is allowed through. - If you are upgrading an existing cluster, run
rad upgrade kubernetesbefore deploying, so the control plane and the registered resource types match the version this integration targets. - A Kubernetes cluster (for example
kind,minikube, AKS) with Radius installed. - The
radCLI on PATH. Use v0.60.2 with the pinned Radius Bicep extension this integration emits (0.60, the published major.minor tag for this release). Runrad versionto check. rad inithas been run against the target cluster so the workspace and environment exist.
Add the integration
From your AppHost directory, add the Aspire.Hosting.Radius integration with the Aspire CLI:
aspire add Aspire.Hosting.Radius
Quick start
In the AppHost.cs file of AppHost, add the environment:
C#
builder.AddRadiusEnvironment("radius");
TypeScript
await builder.addRadiusEnvironment("radius");
That single line is all you add β your existing resource declarations stay the same. The standard Aspire lifecycle still works; Radius participates only in publish and deploy:
| Command | What happens |
|---|---|
aspire run |
Runs your app locally as usual. Radius does no run-mode wiring β the Radius environment is inert during local development and takes effect only at publish/deploy. |
aspire publish |
Generates app.bicep plus a bicepconfig.json pinned to the Radius extension version. |
aspire deploy |
Invokes rad deploy against the generated Bicep β no direct rad knowledge needed for the happy path. |
Publish and deploy:
aspire publish -o radius-artifacts
aspire deploy
Local development with aspire run
Radius is a publish/deploy-only target: aspire run builds and runs your app locally exactly as it
would without Radius, using the normal Aspire dashboard for your resources. The Radius environment
does not attach annotations or alter your resources during local development β Radius wiring happens
when you aspire publish / aspire deploy. You iterate locally as usual, then publish/deploy the
same application resources to a cluster.
Multiple compute environments
When the model contains more than one compute environment (for example a Radius environment alongside a Kubernetes one), explicitly assign each resource to the environment that should publish it:
var radius = builder.AddRadiusEnvironment("radius");
var k8s = builder.AddKubernetesEnvironment("k8s");
builder.AddContainer("api", "myorg/api", "1.0")
.WithComputeEnvironment(radius);
Untargeted resources surface a clear error from the core pipeline instead of being silently claimed by one environment.
Deploying to a cloud
Everything above works against a plain Kubernetes cluster with Radius installed. To target Azure and/or AWS resources, configure the providers in the AppHost.
Cloud providers
Configure Azure and/or AWS cloud providers directly in the AppHost. The publisher
emits the provider configuration on the Radius.Core/environments resource using the
native schema's discrete fields β properties.providers.azure.subscriptionId /
properties.providers.azure.resourceGroupName for Azure and
properties.providers.aws.accountId / properties.providers.aws.region for AWS
(the legacy Applications.Core/environments schema instead used a single scope
path) β and the deploy pipeline registers credentials via rad credential register
before rad deploy runs.
var clientSecret = builder.AddParameter("azure-sp-secret", secret: true);
builder.AddRadiusEnvironment("radius")
.WithAzureProvider(
subscriptionId: "00000000-0000-0000-0000-000000000000",
resourceGroup: "rg-radius",
azure => azure.WithServicePrincipal(
tenantId: "11111111-1111-1111-1111-111111111111",
clientId: "22222222-2222-2222-2222-222222222222",
clientSecret: clientSecret))
.WithAwsProvider(
accountId: "123456789012",
region: "us-west-2",
aws => aws.WithIrsa("arn:aws:iam::123456789012:role/radius-irsa"));
Supported credential modes:
| Provider | Mode | Method |
|---|---|---|
| Azure | Service Principal | azure.WithServicePrincipal(tenantId, clientId, clientSecret) |
| Azure | Workload Identity | azure.WithWorkloadIdentity(tenantId, clientId) |
| AWS | Access Key | aws.WithAccessKey(accessKeyId, secretAccessKey) |
| AWS | IRSA | aws.WithIrsa(iamRoleArn) |
Cloud-provider credential secret material (Azure SP client secret, AWS access-key pair) must be supplied
via builder.AddParameter(..., secret: true). The integration never inlines
those credential values into Bicep or manifests; rad credential register resolves them during
deploy and redacts them from any logged command line. Secret Aspire parameters used in container
environment variables are emitted as @secure() Bicep parameters (never literals) and their values
are supplied to rad deploy separately, not by this credential-registration path.
Security note:
rad credential registeraccepts credential secrets only as command-line arguments, so during registration those resolved values are briefly visible to other users on the same host via the process table (ps//proc/<pid>/cmdline). Log redaction does not mitigate this local, transient exposure. Deploy-time secret parameters do not share this concern β they are written to an owner-only temporary parameters file rather than the command line.
See the Radius cloud providers documentation for an end-to-end walkthrough.
Production & platform features
The features below are power/enterprise capabilities for platform teams. They are opt-in and not needed for a standard single-app deploy β reach for them when your topology demands it.
Recipe parameters
Flow platform values into the Radius recipes that
provision your backing resources with WithRecipeParameters. Parameters can be set environment-wide
(applied to every recipe in the environment) or scoped to a specific Radius resource type
(resource-type-scoped values win on key collision):
var radius = builder.AddRadiusEnvironment("radius")
// Environment-wide: applied to every recipe.
.WithRecipeParameters(p => p["region"] = "eastus")
// Scoped to one resource type; overrides the environment-wide value on collision.
.WithRecipeParameters("Radius.Data/redisCaches", p => p["sku"] = "Premium");
- Type fidelity β numbers, booleans, arrays, and objects are emitted with their Bicep types
(a
6379stays a number, not'6379'). - Aspire parameters flow as
@secure()params, never literals β binding anAddParameter(..., secret: true)value emits a valueless secure Bicepparamand passes the resolved value at deploy time (rad deploy --parameters), so no secret lands in the artifact. - Provider references β reuse a configured cloud provider's scope without re-declaring it, e.g.
p["region"] = RadiusProviderReference.AwsRegion. Referencing a provider that is not configured fails at publish with a message naming the missing provider. - A parameter scoped to a resource type with no emitted recipe is ignored with a warning; the publish still succeeds.
Secret management
Experimental β the secret-store APIs are gated by
ASPIRERADIUS006. Suppress the diagnostic (#pragma warning disable ASPIRERADIUS006) to opt in.
Declare a Radius secret store (Applications.Core/secretStores) and populate it in exactly one
of three ways:
#pragma warning disable ASPIRERADIUS006
// Inline β Radius-created from Aspire secret parameters (@secure() params, redacted at deploy).
var user = builder.AddParameter("db-user", secret: true);
var pass = builder.AddParameter("db-pass", secret: true);
builder.AddRadiusSecretStore("db-creds", RadiusSecretStoreType.BasicAuthentication)
.WithData(d => { d.Add("username", user); d.Add("password", pass); });
// For a single key there is a convenience overload:
// builder.AddRadiusSecretStore("api", RadiusSecretStoreType.Generic).WithData("api-key", apiKey);
// Reference an existing cluster Secret (external operator / hand-applied).
radius.WithSecretStore("tls-cert", RadiusSecretStoreType.Certificate, s =>
s.WithExistingSecret("app/tls-cert", "tls.crt", "tls.key"));
// GitOps sealed secrets β the encrypted manifest is applied before rad deploy and awaited.
radius.WithSecretStore("db-creds", RadiusSecretStoreType.BasicAuthentication, s =>
s.WithSealedSecret("./secrets/db-creds.sealed.yaml", "username", "password"));
- Scope is implied by the API form:
builder.AddRadiusSecretStore(...)is application-scoped (properties.application);radius.WithSecretStore(...)is environment-scoped (properties.environment). - Encoding defaults to
base64forcertificatestores andrawotherwise. - Sealed secrets require
kubectlonPATHand the Bitnami Sealed Secrets controller in the target cluster; the integration applies the already-encrypted manifest (it never runskubeseal) and polls for the materializedSecret(default 120s, overridable viaWithMaterializationTimeout, which applies to sealed stores only βASPIRERADIUS062). - Consume a store from
recipeConfigauth /envSecretsviaWithBicepRegistryAuthentication/WithTerraformGitAuthentication/WithRecipeEnvironmentSecret, referenced by the store's.id.
Reference
Supported resources
AddContainer(...)β published as a Radius container workload (Radius.Compute/containers).AddProject<T>(...)β published as a Radius container workload only when the project has a pre-built image attached withWithContainerImage("<registry>/<image>:<tag>"). Without one,aspire publishfails with a remediation message to build and push an image the cluster can pull. The attached image is treated as final, so files requested throughPublishWithContainerFilesmust be included when the image is built externally.- Selected resources with a Radius mapping emit a Radius resource type via the resource type mapper. Redis, PostgreSQL, and RabbitMQ emit the current
Radius.*user-defined types; MongoDB, SQL Server, and the Dapr building blocks still emit the olderApplications.*portable types, because their UDT equivalents have no published Kubernetes recipe. Child database resources (for exampleAddSqlServer("sql").AddDatabase("appdb")) are collapsed onto the parent today.
Other Aspire resource types are not emitted; only the resources listed above appear in the generated Bicep.
Backing resources and connection information
A backing resource (Redis, PostgreSQL, MongoDB, SQL Server, RabbitMQ) is provisioned by a Radius recipe, not as a Radius.Compute/containers workload. The recipe β not Aspire β decides the Kubernetes Service name and the credentials, so nothing about a backing resource's address can be derived from its Aspire endpoint.
Every value a consumer sees for a backing resource is therefore projected from that resource's own Radius resource:
| Value | Projected from |
|---|---|
Host / port (and anything composed from them: ConnectionStrings__*, *_HOST, *_PORT, *_URI, service discovery) |
<resource>.properties.host / <resource>.properties.port (properties.server for SQL Server, whose legacy type names it that way) |
Password, for resources emitted as legacy Applications.* types (MongoDB, SQL Server) |
<resource>.listSecrets().password β the recipe generates the credential |
| Password, for Redis | Nothing. The pinned kube-recipes/rediscaches recipe deploys Redis without authentication and publishes no password secret, so consumers receive an empty password. A password Aspire generated is dropped with a warning naming the discarded value (ASPIRERADIUS075); a password supplied explicitly fails the publish (ASPIRERADIUS085) |
User name, for resources emitted as legacy Applications.* types (MongoDB, SQL Server) |
<resource>.properties.username β but only when the AppHost supplied one as a parameter (see the known limitations) |
| Password and user name, for PostgreSQL | Written onto the resource's own required username / password schema properties (which the recipe reads as context.resource.properties.<name>), using the same secure Bicep parameter Aspire composes into the connection string, so both sides agree by construction |
| Password, for RabbitMQ | Written into a dedicated Radius.Security/secrets resource, whose resource ID is assigned to the broker's password property β that property takes a secret reference, not a password string. The secret carries the same secure Bicep parameter Aspire composes into the connection string, so both sides agree by construction |
| User name, for RabbitMQ | Written onto the resource's username property, so the broker is provisioned with the same user the connection string names. An explicit user name is required β see ASPIRERADIUS082 |
Because the substitution happens on the underlying values rather than on formatted strings, connection strings, URIs, and the individual connection properties emitted by WithReference all resolve consistently. Values that appear inside a URI are wrapped in uriComponent(...) so a recipe-generated credential containing @, /, or : cannot corrupt the URI.
For resources emitted as legacy Applications.* types (MongoDB, SQL Server), a password or user name the AppHost supplies as a parameter is not used when deploying to Radius β the recipe generates its own. Aspire replaces it with the recipe's value and logs a warning, since the parameter's value never reaches the cluster. Passwords Aspire generated itself are replaced silently. Resources emitted as Radius.* types (PostgreSQL, RabbitMQ) behave the opposite way: the parameter is handed to the recipe β directly on the resource's properties for PostgreSQL, through a Radius.Security/secrets resource for RabbitMQ β so the value the AppHost supplied is the deployed credential and nothing is replaced. Redis is neither: its recipe deploys an unauthenticated server, so a password Aspire generated is discarded with the ASPIRERADIUS075 warning, while a password supplied explicitly (AddRedis("cache", password: ...)) fails the publish with ASPIRERADIUS085 rather than being silently discarded.
Radius additionally injects its own CONNECTION_<NAME>_<PROPERTY> environment variables for every entry in a container's connections block. Those are separate from β and not a replacement for β the ConnectionStrings__* variables Aspire's client integrations read.
Where recipe-generated credentials end up
A recipe-generated credential is emitted as a listSecrets() call in the generated Bicep, so no secret value is written into the published artifacts β an improvement over emitting a resolved credential, and the reason aspire publish output for a backing resource contains no password.
That is a trade rather than a pure reduction, and it is worth understanding before deploying:
- A
@secure()deployment parameter is excluded from ARM/Radius deployment history. Alist*()result assigned to a resource property that is not itself@secure()generally is not, so the resolved credential can appear in deployment records. - Radius's own
connectionscredentials are still emitted as clear-text container environment variables. Aspire does not control that path.
How credential-bearing environment variables are published
A container environment variable is emitted in one of two forms:
valueβ for a value that carries nothing sensitive. It appears verbatim in the KubernetesDeploymentspec, which keeps the deployed app readable.valueFrom.secretKeyRefβ for a value that carries a credential: any value built from a secret parameter (AddParameter(name, secret: true), and the generated passwords Aspire creates for backing resources) or from a recipe'slistSecrets()output. The credential is written to aRadius.Security/secretsresource named<container>-env-secretand referenced by key, so it reaches the pod as a KubernetesSecretand never appears in theDeploymentspec or its rollout history.
The whole composed value moves into the secret, not just its sensitive fragment. That is deliberate: percent-encoding for a value destined for a URI is applied per-fragment by Bicep's uriComponent() at deploy time, whereas kubelet's $(VAR) expansion β the alternative way to compose a secret-backed fragment into a larger string β substitutes at pod start, long after Bicep could have escaped it. A password containing @, :, or / would silently corrupt the connection string. Keeping composition in Bicep preserves the escaping exactly.
One secret is emitted per container, holding every credential-bearing variable keyed by the variable's own name. Kubernetes restricts Secret data keys to letters, digits, -, _, and .; a credential-bearing variable whose name falls outside that set fails the publish with ASPIRERADIUS083 rather than being rejected by the API server at deploy time.
This secret is only ever read by its own container, so it cannot create the secret β resource β secret cycle that a backing resource's credential secret is kept separate to avoid.
Referencing a backing resource that is deployed to a different Radius environment fails the publish with ASPIRERADIUS069: another environment's recipe outputs are not reachable from the generated Bicep. Deploy the consumer and the backing resource to the same environment. The same failure surfaces from the Kubernetes, Azure Container Apps, and Azure App Service publishers when they reference a Radius-owned backing resource, as the public RadiusBackingResourceProjectionException. Only address properties fail this way (Url, Host, IPV4Host, Port, TargetPort, HostAndPort); Scheme and TlsEnabled are read from the endpoint declaration itself and are still answered, unless the endpoint is TLS-enabled β see ASPIRERADIUS081.
Diagnostics
The package uses the ASPIRERADIUS diagnostic prefix for two mechanisms: compile-time
[Experimental] gates on preview APIs, and runtime configuration/publish validation errors.
| Code | Mechanism | Surfaced as |
|---|---|---|
ASPIRERADIUS003 |
Experimental gate on the cloud-provider surface (WithAzureProvider / WithAwsProvider and their credential callbacks) |
[Experimental] warning (suppressible), documented at https://aka.ms/aspire/diagnostics/<id> |
ASPIRERADIUS004 |
Experimental gate on the ConfigureRadiusInfrastructure escape hatch and its construct types |
[Experimental] warning (suppressible) |
ASPIRERADIUS006 |
Experimental gate on the secret-store surface (AddRadiusSecretStore / WithSecretStore and their population/consumer callbacks) |
[Experimental] warning (suppressible) |
ASPIRERADIUS057 |
Experimental gate on WithContainerImage |
[Experimental] warning (suppressible) |
Runtime validation codes:
| Code | When | Meaning |
|---|---|---|
ASPIRERADIUS010 |
Provider config | A cloud-provider credential callback did not select a credential. |
ASPIRERADIUS011 |
Provider config | Conflicting cloud-provider credentials across environments sharing a Radius installation. |
ASPIRERADIUS028 |
Publish | Two recipe parameters bound to distinct Aspire parameters whose names sanitize to the same Bicep identifier. Rename one so they produce distinct identifiers. |
ASPIRERADIUS040βASPIRERADIUS052, ASPIRERADIUS055, ASPIRERADIUS058βASPIRERADIUS059, ASPIRERADIUS061βASPIRERADIUS068 |
Secret-store (AddRadiusSecretStore / WithSecretStore) validation, publish, and deploy |
Thrown ArgumentException (call site, e.g. empty/invalid name or key) / InvalidOperationException (fail-fast validation gate, publish, or deploy). Key codes: 041 (declare exactly one population mode β the zero-mode case), 044 (WithSealedSecret manifest missing/unreadable, malformed, ambiguous/duplicate-key, plaintext-capable, missing a non-empty spec.encryptedData mapping of standard-base64 sealed values, or not a single encrypted Bitnami SealedSecret), 045 (kubectl not on PATH), 046 (WithExistingSecret reference is not a valid [namespace/]name β each segment must be a DNS-1123 label/subdomain), 051 (a secret-store consumer is incompatible with the referenced store β e.g. Bicep-registry auth requires a basicAuthentication (username/password) store, and Terraform Git PAT auth requires a store that declares a pat key), 052 (a key-specific envSecrets consumer references a key a non-empty declared key set does not contain), 058 (the sealed Secret did not materialize before deploy: the Sealed Secrets controller never reported Synced for the applied SealedSecret generation β the controller must have status updates enabled, i.e. not --update-status=false / Helm updateStatus: false), 059 (the active rad workspace's Kubernetes context could not be resolved; publish/deploy fails closed rather than applying the SealedSecret to kubectl's ambient context β configure the active rad workspace or set the ASPIRE_RADIUS_KUBE_CONTEXT override), 061 (the materialized sealed Secret is missing a declared key), 062 (WithMaterializationTimeout was set on a store that is not populated with WithSealedSecret), 063 (a WithSealedSecret manifest embeds a plaintext kind: Secret inside a last-applied-configuration annotation), 064 (a key-specific envSecrets consumer references a store that declares no keys), 065 (a secret store's population was declared more than once β repeated or cross-mode WithData/WithExistingSecret/WithSealedSecret), 066 (a bounded kubectl apply/verify operation exceeded the store's materialization budget and was cancelled), 067 (a secret data key is not a valid Kubernetes Secret key β only alphanumeric characters, -, _, or ., at most 253 characters, and not ./..), 068 (an application-scoped store was declared but the model contains no Radius environment to emit and deploy it β add one with AddRadiusEnvironment). Note: 056 (Bicep identifier collision) and 057 (WithContainerImage experimental) are separate diagnostics documented in their own rows, and codes 053/054/060 are retired. |
ASPIRERADIUS056 |
Publish | Two emitted constructs map to the same Bicep identifier (e.g. a resource named app or recipepack colliding with a synthesized construct, or two resource names that sanitize to the same identifier such as my-x and my.x). Bicep symbols share one flat namespace; rename the conflicting resource. |
ASPIRERADIUS069 |
Publish | A Radius-owned backing resource has no derivable address, so a reference to one of its address properties (Url, Host, IPV4Host, Port, TargetPort, HostAndPort) cannot be resolved. Scheme and TlsEnabled come from the endpoint declaration and are unaffected by this diagnostic (but see ASPIRERADIUS081). Raised as the public RadiusBackingResourceProjectionException, including from other publishers. |
ASPIRERADIUS070 |
Publish | A resource references a parameter that is also a backing resource's credential. The recipe generates that credential, so the referencing resource receives the recipe's value rather than the parameter's. |
ASPIRERADIUS071 |
Publish | An emitted Radius type has no connection schema entry, so neither its address nor its credentials can be projected. This is an internal invariant rather than something an AppHost can trigger or fix β it means a type was added to ResourceTypeMapper without a matching schema row, which EveryEmittedBackingType_HasAConnectionSchema fails the build for. It is asserted at publish time as well because that guard lives in the test suite rather than in the type system, and a mapping added without a schema row must not fall through to the run-mode credential. |
ASPIRERADIUS072 |
Publish | More than one database on a single server resource is referenced by consumers, but the recipe provisions one. |
ASPIRERADIUS073 |
Publish | A ReferenceExpression requested a string format the publisher cannot express in Bicep. |
ASPIRERADIUS074 |
Publish | A projected connection value targets a construct a ConfigureRadiusInfrastructure callback removed. |
ASPIRERADIUS075 |
Publish (warning) | The recipe a backing resource maps to provisions no credential (today: Redis, whose kube-recipes/rediscaches recipe deploys an unauthenticated server). Consumers receive an empty password, and the password Aspire generated for the resource is not applied to the deployed workload. An explicitly supplied password fails the publish instead β see ASPIRERADIUS085. |
ASPIRERADIUS076 |
Publish | A Radius.* type requires username/password as schema properties, but the Aspire resource exposes no such connection property for Aspire to supply, so the deployment would be rejected by schema validation. |
ASPIRERADIUS077 |
Publish | A reference asks a backing resource for something its recipe does not publish: a secondary endpoint (for example RabbitMQ's management endpoint, which the mapped recipe does not deploy), or an endpoint property that has no projection. Fixed by referencing the primary endpoint or removing the reference. |
ASPIRERADIUS078 |
Publish | A conditional ReferenceExpression's condition is only known at deploy time, so the branch to emit cannot be selected while publishing. |
ASPIRERADIUS079 |
Publish | An emitted Radius type has a connection schema, but that schema declares no host or port output, so consumers cannot be given its address. |
ASPIRERADIUS080 |
Publish (warning) | A referenced AddDatabase(...) child names a database the mapped recipe does not create (the legacy SQL Server recipe). |
ASPIRERADIUS081 |
Publish | A TLS-enabled backing endpoint was asked for Scheme, TlsEnabled, or Url. The mapped Radius type publishes no transport-security output, so the value would describe how the resource runs locally rather than how the recipe deploys it. Raised as RadiusBackingResourceProjectionException, including from the Kubernetes, Azure Container Apps, and Azure App Service publishers when they reference a Radius-owned backing resource. |
ASPIRERADIUS082 |
Publish | A RabbitMQ resource would be deployed with the user name guest, which RabbitMQ restricts to loopback connections β the broker would reject every workload that connects to it. Supply an explicit user name with AddRabbitMQ(name, userName: ...). |
ASPIRERADIUS083 |
Publish | A credential-bearing environment variable's name contains characters that a Kubernetes Secret data key may not contain (allowed: letters, digits, -, _, .). Rename the variable. |
ASPIRERADIUS084 |
Publish | A ConfigureRadiusInfrastructure callback removed the Radius.Security/secrets resource a container reads a credential-bearing environment variable from. Keep the resource, or set the variable explicitly in the callback. |
ASPIRERADIUS085 |
Publish | A password was supplied explicitly to a backing resource whose recipe provisions no credential (today: Redis). The deployed server would accept unauthenticated connections, so the publish fails rather than silently dropping the credential. Remove the password, or provision the resource yourself if the workload must authenticate. A password Aspire generated is dropped with the ASPIRERADIUS075 warning instead. |
ASPIRERADIUS086 |
Publish | A backing resource's connection property could not be resolved to a Bicep expression β it is composed from a value that only exists at run time. Raised as RadiusBackingResourceProjectionException. |
ASPIRERADIUS087 |
Publish | A ConfigureRadiusInfrastructure callback left a container environment variable with no usable form: carrying both a value and a valueFrom.secretKeyRef, an incomplete secret reference (only one of SecretName/SecretKey), or none of the three set at all. The value and valueFrom forms are mutually exclusive, and Kubernetes rejects a variable that sets both or neither. Assign either Value or the SecretName/SecretKey pair. A complete reference is checked too: the recipe copies both halves verbatim into the pod's secretKeyRef, so a literal SecretName must be a DNS-1123 subdomain and a literal SecretKey must be a valid Secret data key, or the pod is rejected when it is created. Expression-valued halves are not checked. |
ASPIRERADIUS088 |
Publish | A ConfigureRadiusInfrastructure callback left a secret with a name or a data key that Kubernetes rejects. SecretName becomes the Secret's metadata.name and must be a DNS-1123 subdomain; data keys are copied verbatim and are limited to alphanumerics, -, _, and .. Radius validates neither, so the artifact compiles and fails only when the API server rejects it. Names that are Bicep expressions are not checked, but a secret with no name at all is rejected: it would otherwise emit a resource block with no metadata.name. |
ASPIRERADIUS089 |
Publish | A ConfigureRadiusInfrastructure callback removed or changed the credential a backing resource consumes by resource ID (today: RabbitMQ's password). Consumers were already given the original credential in their connection strings, so the deployed server would require a credential no consumer has β an authentication failure at run time rather than at publish. Supply the credential as a parameter, or point the property at a secret of your own, which hands the whole relationship to the callback. |
ASPIRERADIUS090 |
Publish | Two secrets resolve to the same Kubernetes Secret β the same name in the same namespace β and at least one of them creates it. Radius scopes names by resource type, so an Applications.Core/secretStores and a Radius.Security/secrets can be distinct to Radius yet be one object in the cluster; if both create it the second applied overwrites the first, and if only one creates it the other resolves to contents it did not expect. Two stores in the existing mode are exempt, because referencing different keys of one Secret is the point of that mode. Two sealed stores populated from the same manifest are exempt too β both apply byte-identical content and the re-apply is idempotent, which is how one manifest carrying several keys is split across stores β but two distinct manifests naming one object are a collision, because the second apply replaces the first. This is most easily hit by naming a secret store after a generated container env secret (<container>-env-secret). Rename one of them. |
ASPIRERADIUS091 |
Deploy | The Radius control plane on the target cluster is older than v0.60, whose schemas the generated Bicep targets. Older control planes drop the fields they do not recognize instead of rejecting them, so the deploy would report success while discarding the recipe pack and leaving every backing resource without a recipe. Run rad upgrade kubernetes. A control plane version that cannot be read (cluster unreachable, or an edge build) is allowed through rather than failing the deploy. The check inspects the cluster your active rad workspace targets (honoring the ASPIRE_RADIUS_KUBE_CONTEXT override), not whatever context kubectl happens to be pointing at, so it cannot deliver a verdict about the wrong cluster; if the workspace context cannot be resolved the check is skipped. |
ASPIRERADIUS092 |
Publish | A ConfigureRadiusInfrastructure callback set a secret's kind or a secret store's type to a value that carries a data-shape contract the callback did not satisfy, or to the one spelling that is known to be wrong. Radius.Security/secrets spells the field kind and accepts generic, certificate-pem, certificate-pkcs12, basicAuthentication, azureWorkloadIdentity and awsIRSA; the legacy Applications.Core/secretStores spells it type and accepts certificate where the new type does not, so the bare certificate spelling on a Radius.Security/secrets is reported specifically as a migration mistake. Required keys, which the reference recipes rather than the control plane enforce: certificate-pem (and legacy certificate) need tls.crt/tls.key, basicAuthentication needs username/password, azureWorkloadIdentity needs clientId/tenantId, awsIRSA needs roleARN. The recipe surfaces missing keys as an invalid Kubernetes object name at deploy time rather than as a schema error. Expression-valued kinds and store types are not checked, because a static comparison would be reading the expression's source rather than the value it deploys as. Any other unrecognized literal is rejected: both fields are closed unions of string literals in the types.json the pinned 0.60 extension resolves to, so bicep build fails on an unknown value during rad deploy, and a newer control plane cannot make it valid because the compile step reads the pinned definitions. |
ASPIRERADIUS093 |
Publish | A ConfigureRadiusInfrastructure callback left a Radius.Security/secrets resource missing a field the type requires, or gave a data entry the legacy raw encoding. properties.environment and every entry's value are required β an unset BicepValue is simply omitted from the emitted Bicep, so the artifact compiles and is rejected only by Radius schema validation at deploy. The encoding vocabulary is the one place this type diverges from the legacy Applications.Core/secretStores it replaces: it accepts string and base64, where the legacy type accepted raw and base64. As with ASPIRERADIUS092, encoding is a closed union in the pinned extension types, so any unrecognized literal is rejected; the legacy raw spelling keeps its own message because it is the specific migration mistake worth naming. |
ASPIRERADIUS094 |
Publish | A Radius.Security/secrets resource carrying a credential β either a backing resource's recipe credential or a container's environment variables β was built with no Radius environment to scope it to, and properties.environment is required by the type. Like ASPIRERADIUS071 this is an internal invariant an AppHost cannot trigger: both callers are reached only for resources that force the UDT environment to be emitted (a Radius.* backing type, or a compute workload). It is asserted rather than assumed because emitting the secret without its required scope would produce an artifact Radius rejects at deploy time with nothing naming the cause. |
A variable that WithReference itself injected is exempt from ASPIRERADIUS070 β receiving the recipe's credential is the whole point of the reference. That exemption is decided from the variable name the connection-property splat produces for a reference the resource actually declares (CACHE_PASSWORD, or ADMIN_PASSWORD for WithReference(cache, "admin")), not from the value: an AppHost author can construct a value identical to an injected connection property, and such a variable is silently replaced by the recipe credential, so it is reported rather than exempted.
Connection-property prefixes use the reference's logical name, independently of portable connection-string aliases. For example, WithReference(sql, "db__primary") emits DB__PRIMARY_PASSWORD alongside ConnectionStrings__db_primary; a user-authored DB_PRIMARY_PASSWORD is not an injected property of that reference.
Known limitations
- For
ASPIRERADIUS011, AWS access-key credential conflicts are compared by the Aspire parameter name that supplies the access-key ID, not by the resolved access-key value. Two environments that use different parameter names for the same key can be flagged as a false conflict, while the same parameter name with different values is not flagged. - Application-scoped sealed secret stores are applied by every Radius environment. A benign concurrent re-apply of the same manifest (for example, two environments sharing one store) is tolerated: the wait syncs against the latest live
SealedSecretgeneration and verifies the declared keys materialize. It does not, however, compare the liveSealedSecret's encrypted values against the manifest this deployment applied, so a concurrent writer that replaces the encrypted values while preserving the same key names (only possible if a distinct manifest collides on the same namespace/name) would not be detected. - Recipe customization (per-instance recipes via
PublishAsRadiusResource), multiple Radius resource groups, and cloud-managed resources are not part of this release; they are planned for follow-up releases. - A backing resource's recipe provisions a single database. Referencing more than one database from a single server resource (for example
WithReferenceon twoAddDatabase(...)children of oneAddPostgres(...)) fails the publish withASPIRERADIUS072. Databases are compared by the name they deploy as rather than by Aspire resource name, so several children that are aliases for one physical database (AddDatabase("orders-a", "orders")alongsideAddDatabase("orders-b", "orders")) are all satisfied by the single database the recipe provisions and publish normally. Declaring extra databases nobody references is allowed, but only one is created; a warning names the one that was. Declaring several databases and referencing none of them throughWithReferencealso fails withASPIRERADIUS072: a database consumed only from inside aWithEnvironmentcallback records no reference annotation, so Aspire cannot tell which one the recipe should create. Referencing one database while aWithEnvironmentcallback consumes a different one fails withASPIRERADIUS072too, because the consumer would otherwise receive a connection string naming a database the recipe never creates. A server with noAddDatabase(...)child publishes with a warning, and the recipe is asked to create a database named after the user: the server-level connection string carries no database name, and clients default it to the user name. - A parameter cannot serve as both the user name and the password of one backing resource, and a credential parameter cannot be shared across backing resources when either side's credential is generated by its recipe. Both fail the publish with
ASPIRERADIUS070, because the substitution that projects recipe-generated credentials is keyed by parameter and could only be resolved to one of the two values. - MongoDB and SQL Server are emitted as the legacy
Applications.Datastores/mongoDatabasesandApplications.Datastores/sqlDatabasestypes rather thanRadius.*UDTs. The contrib UDTs (Radius.Data/mongoDatabases,Radius.Data/sqlServerDatabases) do ship in the0.60Bicep extension, so the types exist β what is missing is a published Kubernetes recipe for either of them. Without a recipe there is nothing to deploy, so the legacy types β which have both a published recipe and alistSecrets()action β remain the only deployable option. Each mapping moves to its UDT once the corresponding recipe ships. - The legacy SQL recipe does not create databases. It takes a
databaseparameter but only echoes it back in its outputs: the workload it deploys is a SQL Server with thesalogin and nothing that runsCREATE DATABASE.AddDatabase(...)creates the database in run mode only, so a referencedAddSqlServer("sql").AddDatabase("appdb")hands the consumer a connection string naming a database the deployment does not contain. Publishing warns when this happens. Have the application create the database on startup (for example EF Core'sMigrate()/EnsureCreated()), or deploy SQL Server outside the Radius environment. This resolves whenRadius.Data/sqlServerDatabasesgains a published recipe β its schema declaresdatabaseas required and created. - Redis is deployed without authentication. The recipe the environment pins for Redis (
ghcr.io/radius-project/kube-recipes/rediscaches) starts a bareredisimage with norequirepassand publishes onlyhost/portβ it records no password secret. Consumers therefore receive an empty password. A password Aspire generated forAddRedis(...)is dropped with theASPIRERADIUS075warning; a password supplied explicitly (AddRedis("cache", password: ...)) fails the publish withASPIRERADIUS085, because dropping a credential the AppHost author asked for would deploy an unauthenticated cache without saying so. Deploy Redis as a container, or pair the type with a recipe that configures and publishes a credential, if authentication is required. - Only a backing resource's primary endpoint can be referenced. A Radius recipe publishes a single address, so a reference to a secondary endpoint β
AddRabbitMQ(...).WithManagementPlugin()'smanagementendpoint, for example, which the mappedRadius.Messaging/rabbitMQrecipe does not deploy at all β fails the publish withASPIRERADIUS077rather than projecting the primary address for it. - TLS on a backing resource cannot be projected, because no mapped Radius type publishes a transport-security output. The recipe decides whether the provisioned server terminates TLS, while
EndpointProperty.SchemeandEndpointProperty.TlsEnableddescribe the container Aspire would have run locally β so a TLS-enabled backing endpoint would tell consumersrediss://and,ssl=trueabout a server the recipe starts in plaintext. Rather than emit that, publishing a TLS-enabled backing endpoint fails withASPIRERADIUS081for the properties that carry the decision (Scheme,TlsEnabled, andUrl, which embeds the scheme).Host,Port, andHostAndPortare still projected: the address is right regardless of transport. When a mapped type publishes a TLS output this becomes a projection off that output instead of a failure. - MongoDB's default user name is not projected.
adminis composed into the connection string as literal text with no value to substitute, so it keeps its default value even though the recipe may have created a different user. Supply a user name parameter (AddMongoDB(name, userName: ...)) to get the recipe's user name projected. RabbitMQ is not affected: its user name is an input Aspire writes onto the resource, so the deployed broker is created with whatever the connection string says. - RabbitMQ requires an explicit user name.
AddRabbitMQ("queue")defaults the user name toguest, which RabbitMQ restricts to loopback connections, so a broker provisioned with it rejects every workload pod. Aspire cannot substitute a different name on your behalf either: a default user name reaches the publisher as literal text inside the connection string's format string, with no value to rewrite, so the broker and the connection string would disagree. Publishing therefore fails withASPIRERADIUS082. Pass a user name βAddRabbitMQ("queue", userName: builder.AddParameter("queueuser"))β and both sides use it. Radius.Messaging/rabbitMQcarries aqueueproperty that Aspire does not emit, becauseAddRabbitMQ(...)declares a broker and the Aspire RabbitMQ resource has no queue concept β any value Aspire chose would be invented rather than derived from the AppHost. The property is optional and the recipe defaults it, so the emitted resource still deploys; the queue is simply named by the recipe rather than after anything in the AppHost. There is no way to set it from the AppHost today, and no diagnostic is reported.- URI escaping is emitted as Bicep's
uriComponent(...)and evaluated by the Radius deployment engine, which may leave!'()*unescaped where Aspire's in-process escaping would not. Those characters are legal unescaped in the parts of a URI where credentials appear, so this is a cosmetic difference. - The Redis and RabbitMQ recipes are pinned by commit SHA, not by
:latest.resource-types-contribmoves the:latesttag only when a recipe is part of a stable release, andkube-recipes/rediscachesandkube-recipes/rabbitmqpost-date the last one β:latestdoes not exist for either, and referencing it fails atrad deploywithRecipeDownloadFailedrather than at publish time. The generated artifacts therefore reference an immutable commit-SHA tag, which is the pin Radius itself uses. This is why arecipePacksentry for Redis or RabbitMQ shows a SHA where the other types show:latest. Both move to:latestonce a stable recipe release includes them.
Additional documentation
Feedback & contributing
Changes from Previous Version
Adoption Assessment
A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.