This tutorial walks you through the step of deploying an Adaptive Apps portfolio (min, core,
ent, min-ai, core-ai, ent-ai) to any supported environment
(local k3s, AKS, Azure Local, Azure Arc), and then deploy a sample Radius application.
- docker
- helm
- kubectl
- rad
- (optional) An OpenAI API Key or Azure OpenAI deployment key, when you plan to use cloud-based LLM.
Pick a portfolio and export it. Every subsequent step references $PORTFOLIO — re-export it in any new terminal you open.
export PORTFOLIO=min # or: core | ent | min-ai | core-ai | ent-ai
export RELEASE=$PORTFOLIO # Helm release name; defaults to the portfolio
export NAMESPACE=$PORTFOLIO # Kubernetes namespace for the portfolioIf your target is AKS, define a few more environment variables:
export AZURE_SUBSCRIPTION=<your Azure subscrption id>
export RESOURCE_GROUP=<Azure resource group>
export AKS_CLUSTER=<AKS cluster name>
export AKS_OIDC_ISSUER=$(az aks show \
--resource-group $RESOURCE_GROUP \
--name $AKS_CLUSTER \
--query oidcIssuerProfile.issuerUrl \
-o tsv)| Portfolio | Identity | Service mesh (Istio) | Observability | Governance (OPA) | On-cluster AI (Kaito) |
|---|---|---|---|---|---|
min |
✓ | ✗ | ✗ | ✗ | ✗ |
core |
✓ | ✓ | ✓ | ✗ | ✗ |
ent |
✓ | ✓ | ✓ | ✓ (off by default) | ✗ |
min-ai |
✓ | ✗ | ✗ | ✗ | ✓ |
core-ai |
✓ | ✓ | ✓ | ✗ | ✓ |
ent-ai |
✓ | ✓ | ✓ | ✓ (off by default) | ✓ |
What "On-cluster AI" means here. The
*-aisuffix only controls whether the cluster provisions Kaito so the sample app can serve a model locally. It does not restrict which AI provider the app uses at runtime — any portfolio (includingmin) can be pointed at OpenAI / Azure OpenAI via theaiProvider=openai. Pick*-aionly if you want a self-contained on-cluster model.
This section gets you from "empty cluster context" to "Radius is installed, the portfolio chart is running, and OIDC env vars are exported". Pick one:
| Option | What it covers | When to use |
|---|---|---|
OPTION 1 — ada CLI |
Helm install + Radius install + Keycloak OIDC client provisioning. On --platform k3s it also creates a local k3d cluster. |
Recommended for all platforms. On k8s / AKS / Arc / Azure Local you prepare the cluster yourself first, then point ada at the existing kubectl context. |
| OPTION 2 — Manual | Same steps, one shell command at a time | When you want to see what's happening, or to script just one of the sub-steps. |
-
Set up the CLI per
common/prepare-cli.md. -
Pick the
--platformmatching your target environment. For everything exceptk3s, you must have already prepared a cluster and havekubectlpointed at it (see the prep guides linked in OPTION 2 below).--platformCluster auto-provisioned? Notes k3syes (k3d) one-command end-to-end on your workstation k8sno bring-your-own standard Kubernetes (local or on cloud) aksno adds the managed-Istio helm overrides arcno passthrough — standard kubectl context azure-localno passthrough — standard kubectl context -
Run:
export PLATFORM=k3s # or: k8s | aks | arc | azure-local ada bootstrap \ --portfolio $PORTFOLIO \ --platform $PLATFORM \ --release $RELEASE \ --namespace $NAMESPACE \ --chart-root charts \ --with-radius \ --keep-port-forward
If your are using AKS, you need to pass in three additional parameters:
--azure-subscription,--resource-group, and--aks-cluster.adauses the rg/cluster pair to discover the active Istio add-on revision and (with--with-radius) to federate the Radius service accounts.ada bootstrap \ --portfolio $PORTFOLIO \ --platform aks \ --release $RELEASE \ --namespace $NAMESPACE \ --chart-root charts \ --with-radius \ --azure-subscription $AZURE_SUBSCRIPTION \ --resource-group $RESOURCE_GROUP \ --aks-cluster $AKS_CLUSTER \ --keep-port-forward
What that single command does:
- Cluster — on
--platform k3sonly, creates (or reuses) a local k3d cluster and switches yourkubectlcontext to it. On every other platform this step is a no-op —adauses whatever cluster the currentkubectlcontext points at. For AI portfolios you also need to runcommon/prepare-gpu-kaito.mdbefore invokingada bootstrap. - Portfolio chart —
helm upgrade --installwith the rightcharts/adaptive-apps/profiles/<portfolio>.yamlprofile. Onaksit also adds the managed-Istio overrides. - Radius — installs the control plane, creates the
adaptiveworkspace and group, thetradingenvironment, registers the project's resource types, and deploysradius/local-env.bicep(oraks-env.bicepon--platform aks). - OIDC client — port-forwards to Keycloak, creates/reuses the
adaptive-appsOIDC client, prints anexport OIDC_*block, and (with--keep-port-forward) holds the port-forward open in the foreground.
- Cluster — on
-
Copy the
export OIDC_*lines into the terminal you'll use for step 3 and skip ahead to 3 Deploy the sample app.
Pick one environment and follow the linked guide. Each leaves you with
kubectl pointed at a working cluster:
| Environment | Guide |
|---|---|
| Local k3s (via k3d) | common/prepare-k3s.md |
| Azure Kubernetes Service (AKS) | common/prepare-aks.md |
| Azure Arc-enabled cluster | common/prepare-arc.md |
| Azure Local | common/prepare-azure-local.md |
AI portfolios (*-ai) additionally need GPU + Kaito on the cluster. Follow
common/prepare-gpu-kaito.md before
proceeding.
Use helm install to install the chart, parametrized by a profile
file that turns on the right capabilities for $PORTFOLIO:
helm install $RELEASE charts/adaptive-apps \
-f charts/adaptive-apps/profiles/$PORTFOLIO.yaml \
-n $NAMESPACE --create-namespaceOn AKS, add the managed-Istio overrides (the chart must not install Istio; the AKS add-on owns it):
helm install $RELEASE charts/adaptive-apps \
-f charts/adaptive-apps/profiles/$PORTFOLIO.yaml \
--set features.istio.install=false \
--set istio.namespace=aks-istio-system \
-n $NAMESPACE --create-namespaceVerify:
kubectl get pods -n $NAMESPACEOpen Keycloak and create the OIDC client the sample app will use:
kubectl port-forward -n $NAMESPACE svc/$RELEASE-keycloak 8080:8080In another terminal, open http://localhost:8080, log in as admin / admin
(or whatever you set in keycloak.admin.*), then under realm master:
- Clients → Create client
Client type: OpenID ConnectClient ID:adaptive-apps
- Capability config
Client authentication: OnStandard flow: enabled
- Login settings
Valid redirect URIs:http://localhost:3000/*
- Save, then go to the Credentials tab and copy the client secret.
Export the OIDC env vars (you'll use them in §3):
export OIDC_CLIENT_ID=adaptive-apps
export OIDC_CLIENT_SECRET=<paste-from-credentials-tab>
export OIDC_ISSUER=http://$RELEASE-keycloak.$NAMESPACE.svc.cluster.local:8080/realms/master
export OIDC_AUTH_ENDPOINT=$OIDC_ISSUER/protocol/openid-connect/auth
export OIDC_TOKEN_ENDPOINT=$OIDC_ISSUER/protocol/openid-connect/token
export OIDC_USERINFO_ENDPOINT=$OIDC_ISSUER/protocol/openid-connect/userinfo
export OIDC_BROWSER_AUTH_ENDPOINT=http://localhost:8080/realms/master/protocol/openid-connect/authrad install kubernetes
# Add --set rp.publicEndpointOverride=localhost:<port> if you have a port conflict
rad workspace create kubernetes trading --context "$(kubectl config current-context)" --force
rad workspace switch trading
rad group create adaptive
rad group switch adaptive
rad env create trading --group adaptive --namespace trading-adaptive-apps
rad env switch tradingAKS only — register the Azure credential. The
wi-helper.shyou ran inprepare-aks.mdcreated an Entra app named$AKS_CLUSTER-radius-appand federated it to the Radius service accounts. Now bind it to the Radius control plane (ada bootstrap --with-radius --platform aksdoes this for you):export APPLICATION_CLIENT_ID=$(az ad app list \ --display-name "$AKS_CLUSTER-radius-app" --query '[].appId' -o tsv) export TENANT_ID=$(az account show --query tenantId -o tsv) rad credential register azure wi \ --client-id $APPLICATION_CLIENT_ID --tenant-id $TENANT_ID # Verify (may take 30+ seconds to refresh): rad credential show azure
Register the resource types used by the sample app:
rad resource-type create -f radius/resource-types/types.yamlDeploy the local environment bicep so recipes are registered with Radius:
rad deploy radius/local-env.bicep --group adaptive --environment tradingAKS uses
radius/aks-env.bicepinstead oflocal-env.bicep.
The sample app's backend and frontend containers need Azure managed
identities federated to their Kubernetes service accounts. Run
app-wi-setup.sh once per workload — it creates the managed identity,
federates it, and assigns the needed RBAC. Skip this section on every
other platform.
cd tutorials/getting-started/assets
./app-wi-setup.sh backend $RESOURCE_GROUP $AZURE_SUBSCRIPTION $AKS_OIDC_ISSUER trading default
./app-wi-setup.sh frontend $RESOURCE_GROUP $AZURE_SUBSCRIPTION $AKS_OIDC_ISSUER trading default
export BACKEND_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n backend --query clientId -o tsv)
export FRONTEND_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n frontend --query clientId -o tsv)
cd -Run this once per cluster. Re-deploying the app later does not re-provision identities. Pass
$BACKEND_CLIENT_ID/$FRONTEND_CLIENT_IDtorad deploy radius/app.bicepas parameters wired to the bicep's workload-identity inputs.
Pick how the sample app's AI agent reaches a model. The choice is independent of the portfolio — any portfolio can use either provider:
local— talk to a Kaito-served model running in the cluster. Only works on*-aiportfolios (they're the ones that provision Kaito).openai— call an OpenAI or Azure OpenAI endpoint with the API key from §0. Works on every portfolio.
# Local on-cluster model (requires a *-ai portfolio):
AI_PARAMS=(--parameters aiProvider=local --parameters aiModel=Qwen/Qwen3-0.6B)
# OR — OpenAI / Azure OpenAI (works with any portfolio):
AI_PARAMS=(--parameters aiProvider=openai --parameters aiModelName=gpt-4o --parameters aiApiKey=<OpenAI API Key>)Agent guardrails (AI portfolios only). When
PORTFOLIO=ent-aithe in-pod Agent Governance Toolkit sidecar is the recommended default — opt in by appending--parameters enableAgentGuardrails=trueto therad deploybelow. The sidecar scans every inbound prompt for prompt-injection attempts before it reaches the model. It also works withmin-aiandcore-aion an opt-in basis. The recipe defaults to the Microsoft-publishedghcr.io/microsoft/agentmesh/governance-sidecarimage. See docs/portfolios/overview.md for the per-portfolio matrix. The guardrails parameter is a no-op on non-AI portfolios.
Deploy:
rad deploy radius/app.bicep \
--group adaptive \
--environment trading \
--parameters imageRegistry=ghcr.io/microsoft/adaptive-apps \
--parameters imageTag=latest \
--parameters authUsername=admin \
--parameters authPassword=admin \
--parameters otelCollectorEndpoint=http://otel-collector.$NAMESPACE:4318 \
--parameters oidcIssuer=$OIDC_ISSUER \
--parameters oidcIssuerOverride=http://localhost:8080/realms/master \
--parameters oidcAuthEndpoint=$OIDC_AUTH_ENDPOINT \
--parameters oidcBrowserAuthEndpoint=$OIDC_BROWSER_AUTH_ENDPOINT \
--parameters oidcTokenEndpoint=$OIDC_TOKEN_ENDPOINT \
--parameters oidcUserInfoEndpoint=$OIDC_USERINFO_ENDPOINT \
--parameters oidcClientId=$OIDC_CLIENT_ID \
--parameters oidcClientSecret=$OIDC_CLIENT_SECRET \
"${AI_PARAMS[@]}"Istio portfolios only (core, ent, core-ai, ent-ai) — the app
namespace isn't enrolled in the mesh yet, so pods come up without sidecars.
That's fine for getting the app running; enroll the namespace when you're
ready to exercise mTLS, AuthorizationPolicy, and traffic shaping by
following the service mesh tutorial.
Expose the frontend and open it:
rad resource expose Applications.Core/containers frontend -a adaptive-apps --port 3000 --remote-port 3000Browse to http://localhost:3000. Log in with the local admin account
(admin / admin), or click Sign in with OIDC to authenticate through
Keycloak.
aiProvider=local: The Kaito-managed model pod may take several minutes to become ready while the model is downloaded. Watchkubectl get pods -n trading-adaptive-apps.
rad app delete adaptive-apps
helm uninstall $RELEASE -n $NAMESPACE
kubectl delete namespace $NAMESPACE
# Local k3s only (uses the `k3s` cluster name created by `--platform k3s`):
k3d cluster delete k3sPortfolio-specific operational tutorials build on this baseline:
- Service mesh (
core,ent,core-ai,ent-ai): authoringAuthorizationPolicyandVirtualServiceresources, mTLS troubleshooting. See Service mesh — operator tutorial. - Governance (
ent,ent-ai): enabling OPA via--set features.governance.opa=true --set features.governance.istioExtAuthz=trueand writing Rego policies. See Governance (OPA + Istio) — operator tutorial. - AI (
*-ai): swapping Kaito models, sizing GPU memory, switching between local and cloudaiProvider. (tutorial coming) - Observability: querying traces in Zipkin and metrics in Prometheus, wiring up an external Grafana. (tutorial coming)