Radius Application Modeling
Use this skill for all Radius-related tasks: authoring application Bicep, configuring environments with recipes, and creating custom resource types. This skill covers the full lifecycle from defining an app to deploying it on Kubernetes.
Workflow
- Read the platform constitution. Check for
Platform-Engineering-Constitution.md
in the repository root. Note approved cloud providers, compute platforms, and IaC tooling.
- Check current Radius state. Run , , , and .
- If asked to create an application definition, inspect the workspace first. Look for environment definitions, existing shared resources, container artifacts, and application code that implies required connections.
- Prompt only for missing values. Ask for OCI registry details, boolean choices without a safe default, and long free-text inputs such as AI prompts.
- Author or update the application Bicep, resource types, and/or environment configuration as needed.
- Validate against the constitution and test with .
Interactive App Definition Flow
Use this flow when the user says things like
Create an application definition
,
, or
Scaffold a Radius application
.
Discovery Order
- Inspect environment-definition files first. Treat files such as , , , and similar workspace Bicep files as the primary source of truth for shared resources and environment names.
- Model shared resources from workspace files as in . If PostgreSQL, blob storage, or other shared resources are declared in files like or , reference them with in the generated app definition even if they might not be deployed yet.
- Inspect the repository for container artifacts. Look for , , OCI build configs, or application folders that clearly map to a container workload.
- Choose the container resource type deliberately. Use
Applications.Core/containers
for straightforward single-container application workloads unless the repository explicitly needs recipe-based container features from Radius.Compute/containers
.
- Infer container ports from workload files, not base-image defaults. Prefer explicit signals such as in , directives in , or app server startup arguments over assumptions like nginx using port 80.
- Inspect the code for required connections. Search for connection names, SDK usage, or environment variables that imply dependencies such as PostgreSQL, blob storage, or AI agent endpoints.
- Inspect for AI-agent expectations. If the code expects an agent endpoint or agent-style integration, add a resource.
- Wire connections by responsibility. Connect the AI agent to the data and knowledge resources it needs. Connect the application container only to the AI agent unless the code clearly shows the container itself must talk to other resources directly.
Required Questions
Ask the user only when the value cannot be inferred safely:
- OCI registry host for the container image.
- Enable observability? Ask explicitly for
Radius.AI/agents.enableObservability
because it is boolean and should not be guessed.
- Prompt value for the AI agent.
Authoring Rules For This Flow
- Always create when it does not already exist.
- Always ensure includes the needed Radius extensions for every resource type used in the generated app.
- Use resources for shared infrastructure already defined by the environment, especially PostgreSQL and blob storage when those are pre-provisioned.
- Treat workspace Bicep files as authoritative for shared resources even when those resources are not deployed yet.
- Add a container resource when the repository clearly contains a deployable application container.
- Prefer
Applications.Core/containers
for simple frontend or service containers and only switch to Radius.Compute/containers
when the repo clearly depends on recipe-based container behavior.
- Do not connect the frontend container directly to shared data resources when an AI agent resource already encapsulates those integrations.
- Infer the image repository name from the workload folder or existing repo conventions. If the repo contains , default the image repository to rather than a generic repository name.
- Infer the container port from explicit workload configuration. Prefer , web-server config, or application startup args over generic defaults from the base image.
- Prompt for the OCI registry instead of hardcoding one.
- Prompt for AI observability instead of assuming or .
- If the AI prompt is long or multi-line, do not inline it in the resource. Model it as and set .
- If the AI prompt is short and single-line, it may be inlined unless the user prefers parameterization.
- Prefer minimal app scaffolds that connect to existing shared resources rather than duplicating them.
Expected Output Shape
The generated
should usually include:
- An application resource.
- Existing shared resources such as PostgreSQL and blob storage, when discovered from the environment definition.
- A new application container resource, usually
Applications.Core/containers
for simple app workloads.
- A new resource when the code expects an AI agent.
- Connections from the AI agent to PostgreSQL and blob storage when those resources are required.
- A connection from the application container to the AI agent when the container is only a frontend or thin client.
- Parameters for registry details and long free-text values.
See references/app-definition-flow.md for the canonical example and decision rules.
Customer-Agent Target Profile
When the target repository follows the
structure, use these repo-specific rules:
- Environment file: defines the environment and registered recipes.
- Shared resources file:
radius/shared-resources.bicep
defines PostgreSQL and blob storage that should be referenced with in the generated .
- Frontend container: indicates a user-facing container workload; generate an
Applications.Core/containers
resource such as for it.
- Agent runtime code: indicates the repo expects an AI agent plus PostgreSQL and blob storage connections.
- Agent connections: the generated resource must connect to and so the runtime receives both database and knowledge-base/search inputs through the recipe.
- Frontend connections: the generated container should connect only to the AI agent.
- Frontend image name: the generated container image should use as the repository name unless the user overrides it.
- Frontend port: infer the frontend port from and related config files. For customer-agent, use because the Dockerfile exposes and listens on .
- Do not create a second app-level container for
src/agent-runtime/Dockerfile
when the recipe already encapsulates the agent runtime container.
- Default model: use unless the user asks otherwise.
- Prompt handling: when the user pastes a long or multi-line system prompt, always model it as a rather than inlining it.
Part 1: Application Authoring
Bicep Extension Setup
Before writing
, configure
:
json
{
"extensions": {
"radius": "br:biceptypes.azurecr.io/radius:latest",
"aws": "br:biceptypes.azurecr.io/aws:latest"
},
"experimentalFeaturesEnabled": {
"extensibility": true,
"dynamicTypeLoading": true
}
}
If using resource types (from
), add custom extensions:
json
{
"extensions": {
"radius": "br:biceptypes.azurecr.io/radius:latest",
"aws": "br:biceptypes.azurecr.io/aws:latest",
"radiusCompute": "radius-compute.tgz",
"radiusData": "radius-data.tgz",
"radiusStorage": "radius-storage.tgz",
"radiusSecurity": "radius-security.tgz",
"radiusAi": "radius-ai.tgz"
},
"experimentalFeaturesEnabled": {
"extensibility": true,
"dynamicTypeLoading": true
}
}
Generate custom extensions with:
bash
rad bicep publish-extension --from-file <manifest.yaml> --target <output.tgz>
Resource Type Namespaces
There are two families of resource types:
(Built-in)
Built into Radius.
Applications.Core/containers
is handled directly by the Radius control plane — not recipe-based.
| Type | Description |
|---|
Applications.Core/applications
| Application grouping |
Applications.Core/containers
| Container workloads (directly managed) |
Applications.Core/gateways
| HTTP ingress gateways |
Applications.Datastores/redisCaches
| Redis (recipe-based) |
Applications.Datastores/sqlDatabases
| SQL databases (recipe-based) |
(from radius-resource-types)
Community/extensible types.
ALL are recipe-based, including
Radius.Compute/containers
.
| Type | Description |
|---|
Radius.Compute/containers
| Container workloads (recipe-based) |
Radius.Compute/persistentVolumes
| Persistent storage volumes |
| HTTP routing (requires Gateway API controller) |
Radius.Data/mySqlDatabases
| MySQL databases |
Radius.Data/postgreSqlDatabases
| PostgreSQL databases |
Radius.Storage/blobStorages
| Blob/object storage |
| Secret stores |
| LLM-powered agent runtimes |
is still kept in these skills as a supported custom pattern and example, even though it is not currently listed in the checked-in folders of
.
Critical difference: Applications.Core/containers
is directly managed by Radius.
Radius.Compute/containers
is recipe-based — it needs a registered recipe to deploy.
Application Structure
Using Types (Simpler)
bicep
extension radius
param environment string
param application string
resource frontend 'Applications.Core/containers@2023-10-01-preview' = {
name: 'frontend'
properties: {
application: application
container: { // singular "container"
image: 'myregistry/frontend:latest'
ports: {
web: { containerPort: 3000 }
}
}
connections: {
database: { source: db.id }
}
}
}
resource db 'Applications.Datastores/sqlDatabases@2023-10-01-preview' = {
name: 'database'
properties: {
environment: environment
application: application
}
}
Using Types (Portable)
bicep
extension radius
extension radiusCompute
extension radiusData
param environment string
param application string
resource frontend 'Radius.Compute/containers@2025-08-01-preview' = {
name: 'frontend'
properties: {
environment: environment
application: application
containers: { // plural "containers" — a map!
frontend: {
image: 'myregistry/frontend:latest'
ports: {
web: { containerPort: 3000 }
}
}
}
connections: {
database: { source: db.id }
}
}
}
resource db 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = {
name: 'database'
properties: {
environment: environment
application: application
size: 'S' // Required if recipe expects it
}
}
Schema difference: Applications.Core/containers
uses
(singular object).
Radius.Compute/containers
uses
(plural map where each key is a container name).
Connections and Environment Variables
Applications.Core/containers
— Individual Env Vars
CONNECTION_<NAME>_HOST
CONNECTION_<NAME>_PORT
CONNECTION_<NAME>_DATABASE
CONNECTION_<NAME>_USERNAME
CONNECTION_<NAME>_PASSWORD
Radius.Compute/containers
— JSON Properties Blob
CONNECTION_<NAME>_PROPERTIES={"host":"...","port":"...","database":"..."}
CONNECTION_<NAME>_ID=<resource-id>
CONNECTION_<NAME>_NAME=<connection-name>
CONNECTION_<NAME>_TYPE=<resource-type>
Application code must parse CONNECTION_<NAME>_PROPERTIES
as JSON. Write a helper function that supports both formats:
go
// Go
func getConnProp(connName, prop string) string {
propsJSON := os.Getenv("CONNECTION_" + connName + "_PROPERTIES")
if propsJSON != "" {
var props map[string]interface{}
if err := json.Unmarshal([]byte(propsJSON), &props); err == nil {
if val, ok := props[strings.ToLower(prop)]; ok {
return fmt.Sprintf("%v", val)
}
}
}
return os.Getenv("CONNECTION_" + connName + "_" + prop)
}
javascript
// Node.js
function getConnProp(connName, prop) {
const propsJson = process.env[`CONNECTION_${connName}_PROPERTIES`];
if (propsJson) {
try {
const props = JSON.parse(propsJson);
return props[prop.toLowerCase()] || '';
} catch (e) {}
}
return process.env[`CONNECTION_${connName}_${prop}`] || '';
}
Container Image Requirements
- Cloud registry (ACR, ECR, GHCR): Works if cluster has credentials configured
- Local dev with kind: Push to a local OCI registry and use
host.docker.internal:<port>
as the image host
- : The
Radius.Compute/containers
recipe may set — images must be pullable, not just loaded with
Health Endpoints
Always add
(liveness) and
(readiness) endpoints. The readiness probe should check downstream dependencies.
Part 2: Environment & Recipe Setup
Initialize Radius
bash
rad initialize
rad workspace create kubernetes default --group default --environment default
rad environment create myenv --namespace my-namespace # if needed
rad environment switch myenv
Register Resource Types
bash
# Download YAML from radius-resource-types, then register
rad resource-type create Radius.Data/postgreSqlDatabases --from-file postgreSqlDatabases.yaml
rad resource-type show Radius.Data/postgreSqlDatabases # verify
# Repeat for other repo-backed types such as:
# Radius.Data/mySqlDatabases
# Radius.Storage/blobStorages
# Radius.Security/secrets
# Radius.Compute/persistentVolumes
# Radius.Compute/routes
# Radius.AI/agents
Generate Bicep Extensions
bash
rad bicep publish-extension --from-file postgreSqlDatabases.yaml --target radius-data.tgz
# Then add the matching extension key in bicepconfig.json, for example:
# "radiusData": "radius-data.tgz"
# "radiusStorage": "radius-storage.tgz"
# "radiusSecurity": "radius-security.tgz"
# "radiusAi": "radius-ai.tgz"
Combine multiple types into one YAML (with
separator) to generate a single extension.
Publish and Register Recipes
Critical: Recipes registered from local file paths (
) will NOT work. The Radius control plane runs inside Kubernetes and cannot access the host filesystem. Always publish to an OCI registry.
bash
# Publish to OCI registry
rad bicep publish --file kubernetes-postgresql.bicep \
--target br:myregistry.azurecr.io/recipes/postgresql-kubernetes:latest
# For local/insecure registries, add --plain-http
rad bicep publish --file kubernetes-postgresql.bicep \
--target br:localhost:5001/recipes/postgresql-kubernetes:latest --plain-http
# Register (use host.docker.internal for in-cluster access)
rad recipe register postgresql \
--resource-type Radius.Data/postgreSqlDatabases \
--template-kind bicep \
--template-path "host.docker.internal:5001/recipes/postgresql-kubernetes:latest" \
--plain-http --environment myenv
Recipe Selection Guide
| Constitution Says | Recipe Platform | Recipe IaC | Example |
|---|
| Azure + Terraform | | | |
| Azure + Bicep | | | |
| AWS + Terraform | | | |
| Kubernetes (local) | | or | |
Local Development with kind
1. Local OCI Registry
bash
docker run -d -p 5001:5000 --name radius-registry registry:2
curl http://localhost:5001/v2/_catalog # verify
2. Host Networking
inside a k8s pod does NOT reach the host machine. Use
for all registry and service URLs.
3. Insecure Registry for containerd
bash
NODENAME=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
docker exec $NODENAME mkdir -p /etc/containerd/certs.d/host.docker.internal:5001
docker exec $NODENAME bash -c 'cat > /etc/containerd/certs.d/host.docker.internal:5001/hosts.toml << EOF
[host."http://host.docker.internal:5001"]
capabilities = ["pull", "resolve"]
skip_verify = true
EOF'
docker exec $NODENAME bash -c \
'sed -i "s|config_path = \"\"|config_path = \"/etc/containerd/certs.d\"|" /etc/containerd/config.toml'
docker exec $NODENAME systemctl restart containerd
4. Build and Push Images
bash
docker build -t myapp-backend:latest ./backend
docker tag myapp-backend:latest localhost:5001/myapp-backend:latest
docker push localhost:5001/myapp-backend:latest
In
, reference as
host.docker.internal:5001/myapp-backend:latest
.
Environment Management Commands
bash
rad environment list
rad environment show myenv
rad recipe list --environment myenv
rad recipe show postgresql --resource-type Radius.Data/postgreSqlDatabases --environment myenv
rad resource-type list
rad resource-type show Radius.Data/postgreSqlDatabases
rad workspace show
Part 3: Custom Resource Types & Recipes
Resource Type YAML Schema
yaml
namespace: Radius.Data
types:
postgreSqlDatabases:
description: |
A portable PostgreSQL database resource.
apiVersions:
'2025-08-01-preview':
schema:
type: object
properties:
environment:
type: string
description: "(Required) The Radius Environment ID."
application:
type: string
description: "(Optional) The Radius Application ID."
size:
type: string
enum: ['S', 'M', 'L']
description: "(Optional) The size of the database."
host:
type: string
description: The hostname.
readOnly: true
port:
type: string
description: The port.
readOnly: true
database:
type: string
description: The database name.
readOnly: true
username:
type: string
description: The username.
readOnly: true
password:
type: string
description: The password.
readOnly: true
required: [environment]
Conventions:
- is always required
- Input properties are cloud-agnostic, minimal
- Output properties are marked — they become connection env vars
- Combine multiple types in one YAML under the same namespace
Recipe Directory Structure
<resourceType>/
├── README.md
├── <resourceType>.yaml
└── recipes/
├── kubernetes/bicep/kubernetes-<type>.bicep
├── azure-<service>/bicep/azure-<service>.bicep
└── aws-<service>/terraform/main.tf
Recipe Context Object
context.resource.id // Full resource ID
context.resource.name // Resource name
context.resource.type // e.g., "Radius.Data/postgreSqlDatabases"
context.resource.properties // Developer-set properties from app.bicep
context.runtime.kubernetes.namespace // Target namespace
Important: Use
context.resource.properties.*
(not
).
Bicep Recipe Template
bicep
param context object
var size = contains(context.resource.properties, 'size') ? context.resource.properties.size : 'S'
var name = context.resource.name
var namespace = context.runtime.kubernetes.namespace
// ... deploy k8s resources ...
output result object = {
properties: {
host: '${name}-svc.${namespace}.svc.cluster.local'
port: '5432'
database: name
username: 'admin'
password: 'generated-password'
}
}
Terraform Recipe Template
hcl
variable "context" {
type = any
}
locals {
size = try(var.context.resource.properties.size, "S")
name = var.context.resource.name
namespace = var.context.runtime.kubernetes.namespace
}
output "result" {
value = {
properties = {
host = "${local.name}-svc.${local.namespace}.svc.cluster.local"
port = "5432"
database = local.name
username = "admin"
password = "generated-password"
}
}
}
Publishing and Registration
bash
# 1. Publish recipe to OCI registry
rad bicep publish --file kubernetes-postgresql.bicep \
--target br:myregistry.azurecr.io/recipes/postgresql-kubernetes:latest
# 2. Register resource type
rad resource-type create Radius.Data/postgreSqlDatabases --from-file manifest.yaml
# 3. Register recipe
rad recipe register postgresql \
--resource-type Radius.Data/postgreSqlDatabases \
--template-kind bicep \
--template-path "myregistry.azurecr.io/recipes/postgresql-kubernetes:latest"
# 4. Generate Bicep extension
rad bicep publish-extension --from-file manifest.yaml --target radius-data.tgz
# 5. Test
rad run app.bicep
Common Pitfalls
| Problem | Cause | Fix |
|---|
extension "radius" is not recognized
| Missing | Create it with the Radius extension registry URL |
RecipeDownloadFailed: not a valid repository/tag
| Recipe registered from local file path | Publish to OCI registry, re-register |
RecipeDownloadFailed: connection refused
| used inside k8s pod | Use instead |
RecipeDownloadFailed: HTTPS required
| containerd defaults to HTTPS | Configure in kind node |
property 'size' doesn't exist
| Recipe expects property not set in app.bicep | Add the property or add a safe default in recipe |
| No recipe registered for resource type | |
| Image not accessible from cluster | Push to registry; won't work with pull policy |
| is empty | Using Radius.Compute/containers
(JSON format) | Parse JSON blob instead |
| Bicep validation errors on types | No Bicep extension generated | rad bicep publish-extension
|
| vs schema error | Wrong property name for container type | = singular ; = plural map |
References
| Topic | Reference | Use for |
|---|
| Bicep Patterns | references/bicep-patterns.md | Multi-container apps, gateways, parameterization |
| App Definition Flow | references/app-definition-flow.md | Scaffolding from environment and repo discovery |
| Connection Conventions | references/connection-conventions.md | Env var formats, JSON parsing, portable code |
| Resource Type Catalog | references/resource-type-catalog.md | Available types, schemas, properties |
| Local Development | references/local-development.md | kind, local registry, containerd, Dockerfiles |
| Resource Type YAML | references/resource-type-yaml.md | YAML schema definition format |
| Recipe Authoring | references/recipe-authoring.md | Bicep/Terraform recipes, context object |
| Environment Config | references/environment-config.md | Workspaces, environments, namespaces |
| Cloud Providers | references/cloud-providers.md | Azure, AWS credentials for Radius |
| Recipe Structure | references/recipe-structure.md | Directory layout in radius-resource-types |
| Contribution Guide | references/contribution-guide.md | Contributing to radius-resource-types |
Guardrails
- Always check the platform constitution before suggesting resource types, recipes, or cloud-specific patterns.
- Use portable resource types () instead of cloud-specific resources unless explicitly needed.
- Never hardcode infrastructure details in application definitions — let recipes handle it.
- Always include — required for all Radius resources.
- Handle both connection env var formats ( JSON and individual vars) for portability.
- Set all recipe-expected properties in Bicep (e.g., ), or use safe defaults in recipes.
- Always configure — the Radius Bicep extension won't resolve without it.
- When scaffolding , inspect before asking. Infer shared resources and container workloads from the workspace whenever possible.
- When shared resources already exist in the environment, declare them with instead of provisioning duplicates.
- When shared resources are declared in workspace files such as or , declare them with in the generated instead of duplicating them there.
- Always ask for the OCI registry host when it cannot be inferred from the repository.
- Always ask before setting boolean AI options such as when there is no clear project default.
- Move long or multi-line prompt text into a parameter instead of embedding it directly in the AI agent resource.
- When an app uses an AI agent plus shared data resources, prefer agent-to-resource connections over container-to-resource connections.
- Never register recipes from local file paths — publish to an OCI registry first.
- Use instead of for in-cluster access to host services.
- Use when working with insecure (HTTP) registries.
- Generate Bicep extensions after registering resource types for IDE validation.
- Keep resource type interfaces cloud-agnostic — cloud details belong in recipes, not schemas.
- Always handle missing optional properties in recipes with safe defaults.
- Use (plural map) for and (singular) for .
- Test with before deploying to production.