From 69a3efbd7c5281e8f925bee1e24d0a5e5db292ac Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Mon, 8 Jun 2026 23:47:41 -0500 Subject: [PATCH 01/17] Add Repo Radius feature specification Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 eng/design-notes/github/2026-06-repo-radius.md diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md new file mode 100644 index 00000000000..232f160722b --- /dev/null +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -0,0 +1,223 @@ +# Feature Specification: Repo Radius + +* **Author**: Zach Casper (@zachcasper) + +## Summary + +Today, Radius requires users to install Radius on a Kubernetes cluster. That Kubernetes cluster must have identity and access management configured, gateway controllers installed, storage configured, container image registries set up, and other configurations. Only then can Radius be installed. After these are complete, they must all be performed again for the production environment and Radius installed again. + +This limits Radius adoption to organizations that have a platform engineering team to perform these tasks. For developers without a platform engineering team, Radius is not a viable option. + +Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavy-weight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner with the state stored externally, within the same GitHub account. + +### Top level goals + +* Eliminate the prerequisite to have a Kubernetes cluster configured. +* Enable Radius to run ephemerally within a GitHub Actions runner, starting on demand and shutting down after each operation. +* Persist Radius state (application graphs, deployment history, environment configuration) in GitHub-native storage rather than in-cluster databases. +* Support multiple UX surfaces (Copilot app, CLI, browser extension, third-party integrations), with Copilot app integration as the top priority. +* Maintain compatibility with the existing Radius resource model and Bicep/Terraform recipe system. + +### Non-goals (out of scope) + +* Replacing the existing persistent control plane deployment model. Repo Radius is an alternative delivery mechanism, not a replacement. +* Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). +* Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). +* Multi-repository orchestration at the infrastructure layer. Multi-repo applications (User Story #14) may use Repo Radius per-repo, but cross-repo state coordination is deferred. +* Building the Copilot agent, browser extension, or Deployment panel UI. These are separate components that consume Repo Radius capabilities. + +## User profile and challenges + +### User persona(s) + +The primary user is a **developer** working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and may not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. + +A secondary user is a **platform engineer** evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster, and later migrate to a self-hosted Radius installation if needed (User Story #38). + +### Challenge(s) faced by the user + +* **High barrier to entry.** Before deploying a single application with Radius today, the developer must provision a Kubernetes cluster, configure IAM, install gateway controllers, set up storage, configure a container registry, and install Radius. This takes hours to days and requires Kubernetes expertise. +* **Ongoing operational burden.** A persistent Radius control plane must be upgraded, monitored, and secured. For a developer who just wants to deploy an application, this overhead is disproportionate to the value. +* **Environment duplication.** Separating production from non-production workloads requires separate Kubernetes clusters, each with its own Radius control plane. This doubles the setup and operational cost. +* **No try-before-you-buy path.** There is no lightweight way to experience Radius's value proposition without committing to the full Kubernetes setup. + +### Positive user outcome + +The developer enables Lattice on their GitHub repository and deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored alongside their code in the same GitHub repository. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. + +## Key scenarios + +### Scenario 1: Deploy an application from a GitHub repository + +The developer asks to deploy their application. Cloud resources are provisioned on AWS or Azure without any prior Kubernetes cluster setup, Radius installation, or infrastructure configuration. + +### Scenario 2: Set up a new environment + +The developer creates an environment by providing cloud account details (e.g., AWS account ID and region, or Azure subscription and resource group). The environment is stored as a GitHub Environment and reused across subsequent deployments. + +### Scenario 3: Authorize cloud access + +The developer connects their AWS or Azure account to GitHub using OIDC identity federation so that deployments can provision cloud resources using short-lived tokens rather than stored credentials. + +### Scenario 4: Resume after a previous deployment + +The developer triggers a new deployment days or weeks after the last one. Repo Radius picks up where it left off, loading previously persisted state, without requiring the developer to reconfigure anything. + +### Scenario 5: Migration to self-hosted Radius + +A developer who has outgrown Repo Radius can export their application definitions and environment configurations and migrate to a persistent Radius installation on Kubernetes. + +## Key dependencies + +* **External UI** - Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate UX component. +* **Customizable Terraform backend** - Radius currently stores Terraform state in a Kubernetes secret on the host cluster. Since the Repo Radius cluster is ephemeral, Terraform state must be stored in a persistent external backend. This depends on the Terraform and Bicep Settings feature specification. + +## Key risks + +* **Radius startup time** - Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. +* **State consistency** - Persisting state to orphan branches and repository variables introduces eventual consistency challenges. Concurrent deployments to the same environment could cause conflicts. + +## Key assumptions to test and questions to answer + +* **Reading the Radius data store.** The UX component needs to read application graph data from the Radius data store (e.g., to render a graph visualization or show resource properties). The persisted data store format may not be easily consumable by a Copilot skill or browser extension. The data store format may need to be adapted to support direct reads by UX components. +* **Storage location.** What is the technical storage medium? The previous assumption was used an orphan branch in the Git repository but that has security drawbacks. Are other options within GitHub, such as GHCR, sufficient for all state that Radius needs to persist, or will some state require a different storage mechanism? +* **Concurrency.** How do we handle two developers triggering deployments to the same environment simultaneously? Git-based locking, GitHub environment protection rules, or something else? +* **Credential lifecycle.** How are cloud provider credentials (OIDC tokens) scoped and rotated within the Repo Radius lifecycle? + +## Current state + +Radius currently requires a Kubernetes cluster with a persistent installation. The following related work provides building blocks for Repo Radius: + +* **[Feature Specification: Deploy to External AKS and EKS Clusters](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md)**: Describes the ability to deploy to an AKS or EKS cluster not hosting Radius. Required by Repo Radius since the Kubernetes cluster is now ephemeral. +* **[Terraform and Bicep Settings Feature Specification](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md)**: Describes the ability to store the Terraform backend (state store) outside of Kubernetes. Required by Repo Radius since the Kubernetes cluster is now ephemeral. + * Many of this feature spec's functionality has been implemented in Radius, however, the customizable Terraform backend **has not**. This must be implemented as part of Repo Radius. +* **[GitHub Actions Workspace with Git-Backed State Persistence](https://github.com/radius-project/radius/pull/11457)**: Draft technical design for storing the Radius data store within an orphaned Git branch. Design is likely being modified due to security drawbacks identified. + +## Details of user problem + +When I try to deploy my cloud-native application with Radius, I first have to provision a Kubernetes cluster, configure IAM policies, install ingress controllers, set up persistent storage, configure a container registry, and then install Radius. This process takes hours, requires Kubernetes expertise I may not have, and is error-prone. If I want to separate production from non-production workloads, I need a second cluster and a second Radius installation. + +Once Radius is running, I am responsible for keeping the Kubernetes cluster and the Radius control plane operational: upgrading Radius, monitoring its health, backing up its state, and securing it. This is a significant ongoing cost for what I really want, which is just to deploy my application to AWS or Azure. + +As a result, I either give up on Radius entirely and go back to manually configuring cloud resources, or I wait until my organization hires a platform engineering team, which may never happen. + +## Desired user experience outcome + +After Repo Radius is implemented, a UX component can offer developers a complete deploy-to-cloud experience backed by Radius without requiring the developer to provision any infrastructure. The UX component handles user interaction and intent; Repo Radius handles execution. + +### Detailed user experience + +Repo Radius is a backend designed to be driven by an external UX component (Copilot app, CLI, browser extension, or third-party integration). The UX component interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. + +#### Step 1: Environment setup (prerequisite) + +The UX component uses the GitHub API to create a GitHub Environment. This GitHub Environment becomes the Radius environment when running in Repo Radius mode. The GitHub Environment must specify the following properties as environment variables, corresponding to the properties on `Radius.Core/environments`: + +**For AWS:** + +| Variable | Description | +| --- | --- | +| `AWS_ACCOUNT_ID` | The AWS account ID | +| `AWS_REGION` | The AWS region (e.g., `us-east-1`) | +| `EKS_CLUSTER_NAME` | The EKS cluster name | +| `KUBERNETES_NAMESPACE` | The Kubernetes namespace for application workloads | + +**For Azure:** + +| Variable | Description | +| --- | --- | +| `AZURE_SUBSCRIPTION_ID` | The Azure subscription ID | +| `AZURE_RESOURCE_GROUP` | The Azure resource group | +| `AKS_CLUSTER_NAME` | The AKS cluster name | +| `KUBERNETES_NAMESPACE` | The Kubernetes namespace for application workloads | + +#### Step 2: OIDC setup (prerequisite) + +The UX component guides the user through configuring OIDC federated identity with their cloud provider and updates the GitHub Environment with the required environment variables. + +**For Azure:** + +| Variable | Description | +| --- | --- | +| `AZURE_CLIENT_ID` | The Azure AD application (client) ID | +| `AZURE_TENANT_ID` | The Azure AD tenant ID | + +**For AWS:** + +| Variable | Description | +| --- | --- | +| `AWS_IAM_ROLE_ARN` | The IAM role ARN for OIDC federation | + +#### Step 3: Workflow dispatch + +The UX component calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts a GitHub Environment name and one or more `rad` CLI commands. + +**Single command:** + +```json +POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches +{ + "ref": "main", + "inputs": { + "environment": "production", + "radius_commands": "deploy app.bicep" + } +} +``` + +**Multiple commands:** + +```json +POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches +{ + "ref": "main", + "inputs": { + "environment": "production", + "radius_commands": "[\"deploy app.bicep\", \"app graph\"]" + } +} +``` + +The `radius_commands` input accepts either a single command string or a JSON-encoded array of strings. Each string is a `rad` CLI command with the `rad` prefix omitted. + +#### Step 4: Workflow execution + +The single Repo Radius workflow is executed within a GitHub Actions runner. A prototype of this workflow has been validated. The workflow performs the following stages: + +* **Authenticate with cloud providers.** The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. + +* **Load the Radius data store.** The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. + +* **Create an ephemeral Radius control plane.** The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. + +* **Configure cloud provider credentials.** Cloud credentials from the OIDC exchange are injected into the Radius control plane via the standard `rad credential register` commands. + +* **Configure the Terraform backend.** The workflow configures a persistent Terraform backend outside of the ephemeral cluster so that Terraform state survives across workflow runs. The backend is defined in a `terraform.bicep` file in the `.radius` directory and applied via a `rad deploy terraform.bicep` command included in the `radius_commands` input. + +* **Create a Radius environment based on the GitHub Environment.** The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. + +* **Execute `rad` CLI commands.** The workflow runs each command from the `radius_commands` input (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the UX component. + +* **Persist the Radius data store.** The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. + +## Key investments + +### Investment 1: Deployment to external Kubernetes cluster + +Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. This is required because Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying to the developer's target cluster. + +### Investment 2: Externalization of the Radius data store + +Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, and deployment history. The storage mechanism must be accessible from GitHub Actions and readable by UX components. + +### Investment 3: Repo Radius workflow with standardized inputs and outputs + +Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces structured workflow logs and artifacts as outputs. The input and output contract must be stable and well-defined so that multiple UX components can drive Repo Radius without coupling to its internals. + +### Investment 4: Cloud credential integration + +Integrate with GitHub's OIDC federation to securely provide AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the workflow's authentication steps, `rad credential register` commands, and injection of session credentials into the Radius pods for Terraform provider access. + +### Investment 5: Customizable Terraform backend + +Enable Radius to store Terraform state in a persistent backend outside of the ephemeral Kubernetes cluster. The backend configuration is defined in a `terraform.bicep` file in the `.radius` directory and applied as part of the workflow's `rad` CLI commands. This depends on the Terraform and Bicep Settings feature specification. From 3c30ae41ea78bf1b535abe1619bc8b4206945ebe Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 9 Jun 2026 10:42:47 -0500 Subject: [PATCH 02/17] Added configuration_file input for Terraform backend Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 232f160722b..31b96b3c409 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -23,7 +23,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Replacing the existing persistent control plane deployment model. Repo Radius is an alternative delivery mechanism, not a replacement. * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). -* Multi-repository orchestration at the infrastructure layer. Multi-repo applications (User Story #14) may use Repo Radius per-repo, but cross-repo state coordination is deferred. +* Multi-repository orchestration at the infrastructure layer. Multi-repo applications may use Repo Radius per-repo, but cross-repo state coordination is deferred. * Building the Copilot agent, browser extension, or Deployment panel UI. These are separate components that consume Repo Radius capabilities. ## User profile and challenges @@ -32,7 +32,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius The primary user is a **developer** working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and may not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. -A secondary user is a **platform engineer** evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster, and later migrate to a self-hosted Radius installation if needed (User Story #38). +A secondary user is a **platform engineer** evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster, and later migrate to a self-hosted Radius installation if needed. ### Challenge(s) faced by the user @@ -43,7 +43,7 @@ A secondary user is a **platform engineer** evaluating Radius. Repo Radius provi ### Positive user outcome -The developer enables Lattice on their GitHub repository and deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored alongside their code in the same GitHub repository. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. +The developer, or a solution which uses Repo Radius, creates the Radius workflow in the GitHub repository, then deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. ## Key scenarios @@ -67,15 +67,12 @@ The developer triggers a new deployment days or weeks after the last one. Repo R A developer who has outgrown Repo Radius can export their application definitions and environment configurations and migrate to a persistent Radius installation on Kubernetes. -## Key dependencies +## Key dependencies and risks -* **External UI** - Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate UX component. -* **Customizable Terraform backend** - Radius currently stores Terraform state in a Kubernetes secret on the host cluster. Since the Repo Radius cluster is ephemeral, Terraform state must be stored in a persistent external backend. This depends on the Terraform and Bicep Settings feature specification. - -## Key risks - -* **Radius startup time** - Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. -* **State consistency** - Persisting state to orphan branches and repository variables introduces eventual consistency challenges. Concurrent deployments to the same environment could cause conflicts. +* **Dependency: External UI** - Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate UX component. +* **Dependency: Customizable Terraform backend** - Radius currently stores Terraform state in a Kubernetes secret on the host cluster. Since the Repo Radius cluster is ephemeral, Terraform state must be stored in a persistent external backend. This depends on the Terraform and Bicep Settings feature specification. +* **Risk: Radius startup time** - Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. +* **Risk: State consistency** - Persisting state to orphan branches and repository variables introduces eventual consistency challenges. Concurrent deployments to the same environment could cause conflicts. ## Key assumptions to test and questions to answer @@ -86,12 +83,12 @@ A developer who has outgrown Repo Radius can export their application definition ## Current state -Radius currently requires a Kubernetes cluster with a persistent installation. The following related work provides building blocks for Repo Radius: +Work on many Repo Radius components or dependencies is already in flight. This includes: -* **[Feature Specification: Deploy to External AKS and EKS Clusters](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md)**: Describes the ability to deploy to an AKS or EKS cluster not hosting Radius. Required by Repo Radius since the Kubernetes cluster is now ephemeral. -* **[Terraform and Bicep Settings Feature Specification](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md)**: Describes the ability to store the Terraform backend (state store) outside of Kubernetes. Required by Repo Radius since the Kubernetes cluster is now ephemeral. - * Many of this feature spec's functionality has been implemented in Radius, however, the customizable Terraform backend **has not**. This must be implemented as part of Repo Radius. -* **[GitHub Actions Workspace with Git-Backed State Persistence](https://github.com/radius-project/radius/pull/11457)**: Draft technical design for storing the Radius data store within an orphaned Git branch. Design is likely being modified due to security drawbacks identified. +* **Deploy to External AKS and EKS Clusters**: [Feature spec](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md) is reviewed and merged. +* **Externalize Radius state store**: The [technical design](https://github.com/radius-project/radius/pull/11457) is in draft. +* **Workflow including OIDC authentication**: A [prototype workflow](https://github.com/radius-project/github-extension/blob/0378b1e349d09fe0f7f09c978c0e7a32c214a72a/.copilot/extensions/radius/src/shared/github-client.ts#L1581) has been implemented which includes OIDC authentication to AWS and Azure. The two differences between the prototype workflow and this spec are (1) the standardized workflow inputs and outputs and (2) loading of the external Radius state store. +* **Customizable Terraform backend**: The [Terraform and Bicep Settings Feature Specification](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md) includes details for supporting Terraform backends (the state store) outside the Kubernetes cluster. ## Details of user problem @@ -107,7 +104,7 @@ After Repo Radius is implemented, a UX component can offer developers a complete ### Detailed user experience -Repo Radius is a backend designed to be driven by an external UX component (Copilot app, CLI, browser extension, or third-party integration). The UX component interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. +Repo Radius is a backend designed to be driven by an external UX component (Copilot app, CLI, browser extension, or other integration). The UX component interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. #### Step 1: Environment setup (prerequisite) @@ -150,7 +147,13 @@ The UX component guides the user through configuring OIDC federated identity wit #### Step 3: Workflow dispatch -The UX component calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts a GitHub Environment name and one or more `rad` CLI commands. +The UX component calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts the following inputs: + +| Input | Required | Description | +| --- | --- | --- | +| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | +| `radius_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command with the `rad` prefix omitted. | +| `configuration_file` | No | Path to a Bicep file deployed before `radius_commands` to configure the Radius control plane (e.g., Terraform backend settings). Defaults to `.radius/radius.bicep`. | **Single command:** @@ -165,7 +168,7 @@ POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches } ``` -**Multiple commands:** +**Multiple commands with explicit configuration file:** ```json POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches @@ -173,13 +176,12 @@ POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches "ref": "main", "inputs": { "environment": "production", + "configuration_file": ".radius/radius.bicep", "radius_commands": "[\"deploy app.bicep\", \"app graph\"]" } } ``` -The `radius_commands` input accepts either a single command string or a JSON-encoded array of strings. Each string is a `rad` CLI command with the `rad` prefix omitted. - #### Step 4: Workflow execution The single Repo Radius workflow is executed within a GitHub Actions runner. A prototype of this workflow has been validated. The workflow performs the following stages: @@ -192,11 +194,11 @@ The single Repo Radius workflow is executed within a GitHub Actions runner. A pr * **Configure cloud provider credentials.** Cloud credentials from the OIDC exchange are injected into the Radius control plane via the standard `rad credential register` commands. -* **Configure the Terraform backend.** The workflow configures a persistent Terraform backend outside of the ephemeral cluster so that Terraform state survives across workflow runs. The backend is defined in a `terraform.bicep` file in the `.radius` directory and applied via a `rad deploy terraform.bicep` command included in the `radius_commands` input. +* **Deploy the configuration file.** If the `configuration_file` input is provided (or the default `.radius/radius.bicep` exists), the workflow runs `rad deploy `. This file configures the Radius control plane for the repository, including the Terraform backend location and other settings. * **Create a Radius environment based on the GitHub Environment.** The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. -* **Execute `rad` CLI commands.** The workflow runs each command from the `radius_commands` input (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the UX component. +* **Execute `rad` CLI commands.** The workflow parses the `radius_commands` input and runs each command (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the UX component. * **Persist the Radius data store.** The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. @@ -220,4 +222,4 @@ Integrate with GitHub's OIDC federation to securely provide AWS and Azure creden ### Investment 5: Customizable Terraform backend -Enable Radius to store Terraform state in a persistent backend outside of the ephemeral Kubernetes cluster. The backend configuration is defined in a `terraform.bicep` file in the `.radius` directory and applied as part of the workflow's `rad` CLI commands. This depends on the Terraform and Bicep Settings feature specification. +Enable Radius to store Terraform state in a persistent backend outside of the ephemeral Kubernetes cluster. The backend configuration is defined in the `configuration_file` (default `.radius/radius.bicep`) and deployed before user commands. This depends on the Terraform and Bicep Settings feature specification. From e6d633459a3bc15d21e66169cbb7ef6755369ad2 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 9 Jun 2026 16:39:10 -0500 Subject: [PATCH 03/17] Addressed comments Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 31b96b3c409..a4ec7b7ca76 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -8,7 +8,7 @@ Today, Radius requires users to install Radius on a Kubernetes cluster. That Kub This limits Radius adoption to organizations that have a platform engineering team to perform these tasks. For developers without a platform engineering team, Radius is not a viable option. -Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavy-weight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner with the state stored externally, within the same GitHub account. +Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavy-weight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner, with state stored externally in the same GitHub account. ### Top level goals @@ -24,7 +24,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Multi-repository orchestration at the infrastructure layer. Multi-repo applications may use Repo Radius per-repo, but cross-repo state coordination is deferred. -* Building the Copilot agent, browser extension, or Deployment panel UI. These are separate components that consume Repo Radius capabilities. +* Building the Copilot agent, browser extension, Deployment panel UI, or other frontend components. These are separate components that consume Repo Radius capabilities. ## User profile and challenges @@ -36,14 +36,14 @@ A secondary user is a **platform engineer** evaluating Radius. Repo Radius provi ### Challenge(s) faced by the user -* **High barrier to entry.** Before deploying a single application with Radius today, the developer must provision a Kubernetes cluster, configure IAM, install gateway controllers, set up storage, configure a container registry, and install Radius. This takes hours to days and requires Kubernetes expertise. -* **Ongoing operational burden.** A persistent Radius control plane must be upgraded, monitored, and secured. For a developer who just wants to deploy an application, this overhead is disproportionate to the value. -* **Environment duplication.** Separating production from non-production workloads requires separate Kubernetes clusters, each with its own Radius control plane. This doubles the setup and operational cost. -* **No try-before-you-buy path.** There is no lightweight way to experience Radius's value proposition without committing to the full Kubernetes setup. +* **High barrier to entry**. Before deploying a single application with Radius today, the developer must provision a Kubernetes cluster, configure IAM, install gateway controllers, set up storage, configure a container registry, and install Radius. This takes hours to days and requires Kubernetes expertise. +* **Ongoing operational burden**. A persistent Radius control plane must be upgraded, monitored, and secured. For a developer who just wants to deploy an application, this overhead is disproportionate to the value. +* **Environment duplication**. Separating production from non-production workloads requires separate Kubernetes clusters, each with its own Radius control plane. This doubles the setup and operational cost. +* **No try-before-you-buy path**. There is no lightweight way to experience Radius's value proposition without committing to the full Kubernetes setup. ### Positive user outcome -The developer, or a solution which uses Repo Radius, creates the Radius workflow in the GitHub repository, then deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. +The developer, or a solution built on Repo Radius, creates the Radius workflow in the GitHub repository, then deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. ## Key scenarios @@ -59,9 +59,9 @@ The developer creates an environment by providing cloud account details (e.g., A The developer connects their AWS or Azure account to GitHub using OIDC identity federation so that deployments can provision cloud resources using short-lived tokens rather than stored credentials. -### Scenario 4: Resume after a previous deployment +### Scenario 4: Update a deployed application -The developer triggers a new deployment days or weeks after the last one. Repo Radius picks up where it left off, loading previously persisted state, without requiring the developer to reconfigure anything. +The developer modifies their application and deploys the change to an existing environment. Repo Radius loads the persisted state from the previous deployment and applies changes incrementally. ### Scenario 5: Migration to self-hosted Radius @@ -69,26 +69,23 @@ A developer who has outgrown Repo Radius can export their application definition ## Key dependencies and risks -* **Dependency: External UI** - Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate UX component. -* **Dependency: Customizable Terraform backend** - Radius currently stores Terraform state in a Kubernetes secret on the host cluster. Since the Repo Radius cluster is ephemeral, Terraform state must be stored in a persistent external backend. This depends on the Terraform and Bicep Settings feature specification. -* **Risk: Radius startup time** - Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. -* **Risk: State consistency** - Persisting state to orphan branches and repository variables introduces eventual consistency challenges. Concurrent deployments to the same environment could cause conflicts. +* **Dependency: External UI**. Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate frontend component. +* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. ## Key assumptions to test and questions to answer -* **Reading the Radius data store.** The UX component needs to read application graph data from the Radius data store (e.g., to render a graph visualization or show resource properties). The persisted data store format may not be easily consumable by a Copilot skill or browser extension. The data store format may need to be adapted to support direct reads by UX components. -* **Storage location.** What is the technical storage medium? The previous assumption was used an orphan branch in the Git repository but that has security drawbacks. Are other options within GitHub, such as GHCR, sufficient for all state that Radius needs to persist, or will some state require a different storage mechanism? -* **Concurrency.** How do we handle two developers triggering deployments to the same environment simultaneously? Git-based locking, GitHub environment protection rules, or something else? -* **Credential lifecycle.** How are cloud provider credentials (OIDC tokens) scoped and rotated within the Repo Radius lifecycle? +* **Reading application graph data**. Application graph data must be readable by the frontend without the Radius control plane being available and separate from the Radius control plane state. In the current designs for the application graph, graph data is stored separately from Repo Radius. +* **Storage location**. What is the technical storage medium? The previous assumption used an orphan branch in the Git repository but that has security drawbacks. Are other options within GitHub, such as GHCR, sufficient for all state that Radius needs to persist, or will some state require a different storage mechanism? +* **Concurrency**. How do we handle two developers triggering deployments to the same environment simultaneously? Git-based locking, GitHub environment protection rules, or something else? +* **Credential lifecycle**. How are cloud provider credentials (OIDC tokens) scoped and rotated within the Repo Radius lifecycle? ## Current state Work on many Repo Radius components or dependencies is already in flight. This includes: * **Deploy to External AKS and EKS Clusters**: [Feature spec](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md) is reviewed and merged. -* **Externalize Radius state store**: The [technical design](https://github.com/radius-project/radius/pull/11457) is in draft. -* **Workflow including OIDC authentication**: A [prototype workflow](https://github.com/radius-project/github-extension/blob/0378b1e349d09fe0f7f09c978c0e7a32c214a72a/.copilot/extensions/radius/src/shared/github-client.ts#L1581) has been implemented which includes OIDC authentication to AWS and Azure. The two differences between the prototype workflow and this spec are (1) the standardized workflow inputs and outputs and (2) loading of the external Radius state store. -* **Customizable Terraform backend**: The [Terraform and Bicep Settings Feature Specification](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md) includes details for supporting Terraform backends (the state store) outside the Kubernetes cluster. +* **Externalize Radius state store**: The [technical design](https://github.com/radius-project/radius/pull/11457) is in draft but is expected to be superseded by a revised technical design. +* **Workflow including OIDC authentication**: A [prototype workflow](https://github.com/radius-project/github-extension/blob/0378b1e349d09fe0f7f09c978c0e7a32c214a72a/.copilot/extensions/radius/src/shared/github-client.ts#L1581) has been implemented which includes OIDC authentication to AWS and Azure. This spec extends the prototype with (1) standardized workflow inputs and outputs and (2) loading of the external Radius state store. ## Details of user problem @@ -100,15 +97,15 @@ As a result, I either give up on Radius entirely and go back to manually configu ## Desired user experience outcome -After Repo Radius is implemented, a UX component can offer developers a complete deploy-to-cloud experience backed by Radius without requiring the developer to provision any infrastructure. The UX component handles user interaction and intent; Repo Radius handles execution. +After Repo Radius is implemented, a frontend can offer developers a complete deploy-to-cloud experience backed by Radius without requiring the developer to provision any infrastructure. The frontend handles user interaction and intent; Repo Radius handles execution. ### Detailed user experience -Repo Radius is a backend designed to be driven by an external UX component (Copilot app, CLI, browser extension, or other integration). The UX component interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. +Repo Radius is a backend designed to be driven by a separate frontend component (Copilot app, CLI, browser extension, or other integration). The frontend interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. #### Step 1: Environment setup (prerequisite) -The UX component uses the GitHub API to create a GitHub Environment. This GitHub Environment becomes the Radius environment when running in Repo Radius mode. The GitHub Environment must specify the following properties as environment variables, corresponding to the properties on `Radius.Core/environments`: +The frontend uses the GitHub API to create a GitHub Environment. This GitHub Environment becomes the Radius environment when running in Repo Radius mode. The GitHub Environment must specify the following properties as environment variables, corresponding to the properties on `Radius.Core/environments`: **For AWS:** @@ -130,7 +127,7 @@ The UX component uses the GitHub API to create a GitHub Environment. This GitHub #### Step 2: OIDC setup (prerequisite) -The UX component guides the user through configuring OIDC federated identity with their cloud provider and updates the GitHub Environment with the required environment variables. +The frontend guides the user through configuring OIDC federated identity with their cloud provider and updates the GitHub Environment with the required environment variables. **For Azure:** @@ -147,13 +144,12 @@ The UX component guides the user through configuring OIDC federated identity wit #### Step 3: Workflow dispatch -The UX component calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts the following inputs: +The frontend calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts the following inputs: | Input | Required | Description | | --- | --- | --- | | `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `radius_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command with the `rad` prefix omitted. | -| `configuration_file` | No | Path to a Bicep file deployed before `radius_commands` to configure the Radius control plane (e.g., Terraform backend settings). Defaults to `.radius/radius.bicep`. | +| `radius_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command, to be executed in order, with the `rad` prefix omitted. | **Single command:** @@ -168,7 +164,7 @@ POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches } ``` -**Multiple commands with explicit configuration file:** +**Multiple commands:** ```json POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches @@ -176,7 +172,6 @@ POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches "ref": "main", "inputs": { "environment": "production", - "configuration_file": ".radius/radius.bicep", "radius_commands": "[\"deploy app.bicep\", \"app graph\"]" } } @@ -186,21 +181,19 @@ POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches The single Repo Radius workflow is executed within a GitHub Actions runner. A prototype of this workflow has been validated. The workflow performs the following stages: -* **Authenticate with cloud providers.** The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. +* **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. -* **Load the Radius data store.** The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. +* **Load the Radius data store**. The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. -* **Create an ephemeral Radius control plane.** The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. +* **Create an ephemeral Radius control plane**. The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. -* **Configure cloud provider credentials.** Cloud credentials from the OIDC exchange are injected into the Radius control plane via the standard `rad credential register` commands. +* **Configure cloud provider credentials**. Cloud credentials from the OIDC exchange are injected into the Radius control plane via the standard `rad credential register` commands. -* **Deploy the configuration file.** If the `configuration_file` input is provided (or the default `.radius/radius.bicep` exists), the workflow runs `rad deploy `. This file configures the Radius control plane for the repository, including the Terraform backend location and other settings. +* **Create a Radius environment based on the GitHub Environment**. The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. -* **Create a Radius environment based on the GitHub Environment.** The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. +* **Execute `rad` CLI commands**. The workflow parses the `radius_commands` input and runs each command (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the frontend. -* **Execute `rad` CLI commands.** The workflow parses the `radius_commands` input and runs each command (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the UX component. - -* **Persist the Radius data store.** The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. +* **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. ## Key investments @@ -210,16 +203,14 @@ Enable Radius to deploy application workloads to an AKS or EKS cluster that is s ### Investment 2: Externalization of the Radius data store -Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, and deployment history. The storage mechanism must be accessible from GitHub Actions and readable by UX components. +Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, deployment history, and Terraform state. ### Investment 3: Repo Radius workflow with standardized inputs and outputs -Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces structured workflow logs and artifacts as outputs. The input and output contract must be stable and well-defined so that multiple UX components can drive Repo Radius without coupling to its internals. +Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces structured workflow logs and artifacts as outputs. The input and output contract must be stable and well-defined so that multiple frontend components can drive Repo Radius without coupling to its internals. ### Investment 4: Cloud credential integration Integrate with GitHub's OIDC federation to securely provide AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the workflow's authentication steps, `rad credential register` commands, and injection of session credentials into the Radius pods for Terraform provider access. -### Investment 5: Customizable Terraform backend -Enable Radius to store Terraform state in a persistent backend outside of the ephemeral Kubernetes cluster. The backend configuration is defined in the `configuration_file` (default `.radius/radius.bicep`) and deployed before user commands. This depends on the Terraform and Bicep Settings feature specification. From c75c2ef7c3bc6cdca8ee67e0524c286dc99e35e4 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 9 Jun 2026 16:46:23 -0500 Subject: [PATCH 04/17] Simplified step 1 and 2 Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index a4ec7b7ca76..351aede4232 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -105,42 +105,11 @@ Repo Radius is a backend designed to be driven by a separate frontend component #### Step 1: Environment setup (prerequisite) -The frontend uses the GitHub API to create a GitHub Environment. This GitHub Environment becomes the Radius environment when running in Repo Radius mode. The GitHub Environment must specify the following properties as environment variables, corresponding to the properties on `Radius.Core/environments`: - -**For AWS:** - -| Variable | Description | -| --- | --- | -| `AWS_ACCOUNT_ID` | The AWS account ID | -| `AWS_REGION` | The AWS region (e.g., `us-east-1`) | -| `EKS_CLUSTER_NAME` | The EKS cluster name | -| `KUBERNETES_NAMESPACE` | The Kubernetes namespace for application workloads | - -**For Azure:** - -| Variable | Description | -| --- | --- | -| `AZURE_SUBSCRIPTION_ID` | The Azure subscription ID | -| `AZURE_RESOURCE_GROUP` | The Azure resource group | -| `AKS_CLUSTER_NAME` | The AKS cluster name | -| `KUBERNETES_NAMESPACE` | The Kubernetes namespace for application workloads | +The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKA or AKS cluster name and Kubernetes namespace) for application workloads. #### Step 2: OIDC setup (prerequisite) -The frontend guides the user through configuring OIDC federated identity with their cloud provider and updates the GitHub Environment with the required environment variables. - -**For Azure:** - -| Variable | Description | -| --- | --- | -| `AZURE_CLIENT_ID` | The Azure AD application (client) ID | -| `AZURE_TENANT_ID` | The Azure AD tenant ID | - -**For AWS:** - -| Variable | Description | -| --- | --- | -| `AWS_IAM_ROLE_ARN` | The IAM role ARN for OIDC federation | +The frontend guides the user through configuring OIDC federated identity with their cloud provider so that the workflow can authenticate using short-lived tokens rather than stored credentials. The Azure client and tenat ID and AWS IAM role are stored in the GitHub Environment. #### Step 3: Workflow dispatch From 1c36d2dac3cafeaaafd55ac2b57b43968296cf36 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Wed, 10 Jun 2026 11:26:02 -0500 Subject: [PATCH 05/17] Clarified workflow outputs Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 351aede4232..4d248b52093 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -70,13 +70,19 @@ A developer who has outgrown Repo Radius can export their application definition ## Key dependencies and risks * **Dependency: External UI**. Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate frontend component. + * **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. ## Key assumptions to test and questions to answer * **Reading application graph data**. Application graph data must be readable by the frontend without the Radius control plane being available and separate from the Radius control plane state. In the current designs for the application graph, graph data is stored separately from Repo Radius. + * **Storage location**. What is the technical storage medium? The previous assumption used an orphan branch in the Git repository but that has security drawbacks. Are other options within GitHub, such as GHCR, sufficient for all state that Radius needs to persist, or will some state require a different storage mechanism? + +* **Real-time deployment status**. How does the frontend receive real-time progress updates during a workflow run? The GitHub REST API does not support streaming job logs while a job is running. This feature spec includes the creation of artifacts after each Radius CLI command is executed, but GitHub Actions does not provide a public API for streaming updates (e.g., resource-by-resource updates from the `rad deploy` command). Is it possible, and if so, what mechanism should the workflow use to provide incremental status to the frontend? + * **Concurrency**. How do we handle two developers triggering deployments to the same environment simultaneously? Git-based locking, GitHub environment protection rules, or something else? + * **Credential lifecycle**. How are cloud provider credentials (OIDC tokens) scoped and rotated within the Repo Radius lifecycle? ## Current state @@ -84,7 +90,9 @@ A developer who has outgrown Repo Radius can export their application definition Work on many Repo Radius components or dependencies is already in flight. This includes: * **Deploy to External AKS and EKS Clusters**: [Feature spec](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md) is reviewed and merged. + * **Externalize Radius state store**: The [technical design](https://github.com/radius-project/radius/pull/11457) is in draft but is expected to be superseded by a revised technical design. + * **Workflow including OIDC authentication**: A [prototype workflow](https://github.com/radius-project/github-extension/blob/0378b1e349d09fe0f7f09c978c0e7a32c214a72a/.copilot/extensions/radius/src/shared/github-client.ts#L1581) has been implemented which includes OIDC authentication to AWS and Azure. This spec extends the prototype with (1) standardized workflow inputs and outputs and (2) loading of the external Radius state store. ## Details of user problem @@ -93,7 +101,7 @@ When I try to deploy my cloud-native application with Radius, I first have to pr Once Radius is running, I am responsible for keeping the Kubernetes cluster and the Radius control plane operational: upgrading Radius, monitoring its health, backing up its state, and securing it. This is a significant ongoing cost for what I really want, which is just to deploy my application to AWS or Azure. -As a result, I either give up on Radius entirely and go back to manually configuring cloud resources, or I wait until my organization hires a platform engineering team, which may never happen. +As a result, I fall back to my existing infrastructure as code solution and do not adopt Radius. ## Desired user experience outcome @@ -105,11 +113,11 @@ Repo Radius is a backend designed to be driven by a separate frontend component #### Step 1: Environment setup (prerequisite) -The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKA or AKS cluster name and Kubernetes namespace) for application workloads. +The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKS or AKS cluster name and Kubernetes namespace) for application workloads. #### Step 2: OIDC setup (prerequisite) -The frontend guides the user through configuring OIDC federated identity with their cloud provider so that the workflow can authenticate using short-lived tokens rather than stored credentials. The Azure client and tenat ID and AWS IAM role are stored in the GitHub Environment. +The frontend guides the user through configuring their cloud provider to trust the GitHub Actions OIDC identity provider as a federated identity source, so that the workflow can authenticate using short-lived tokens rather than stored credentials. For Azure, this means creating a federated credential on an Entra ID app registration that trusts tokens issued by GitHub Actions; the resulting client ID, tenant ID, and subscription ID are stored in the GitHub Environment. For AWS, this means creating an IAM OIDC identity provider and an IAM role with a trust policy that accepts GitHub Actions tokens; the role ARN and region are stored in the GitHub Environment. #### Step 3: Workflow dispatch @@ -160,9 +168,13 @@ The single Repo Radius workflow is executed within a GitHub Actions runner. A pr * **Create a Radius environment based on the GitHub Environment**. The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. -* **Execute `rad` CLI commands**. The workflow parses the `radius_commands` input and runs each command (e.g., `rad deploy app.bicep`). The workflow provides structured updates for each step in the workflow log. The output of each command is captured as a GitHub Actions workflow artifact for post-execution consumption by the frontend. +* **Execute `rad` CLI commands**. The workflow iterates over the `radius_commands` input. For each command, the workflow executes the command, captures its output, and uploads the output as a workflow artifact. If a command fails, the workflow stops iteration and proceeds to persist the data store. + +* **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. + +#### Step 5: Read workflow output -* **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. +The frontend reads the output of the workflow by polling the GitHub Actions API for the workflow run status and downloading artifacts as they become available. Artifacts are available via the API as soon as each upload step completes, so the frontend can retrieve results incrementally while the workflow is still running. ## Key investments @@ -172,14 +184,12 @@ Enable Radius to deploy application workloads to an AKS or EKS cluster that is s ### Investment 2: Externalization of the Radius data store -Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, deployment history, and Terraform state. +Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, deployment history, and Terraform state. ### Investment 3: Repo Radius workflow with standardized inputs and outputs -Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces structured workflow logs and artifacts as outputs. The input and output contract must be stable and well-defined so that multiple frontend components can drive Repo Radius without coupling to its internals. +Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces artifacts as outputs. The input and output contract must be stable and well-defined so that multiple frontend components can drive Repo Radius without coupling to its internals. ### Investment 4: Cloud credential integration Integrate with GitHub's OIDC federation to securely provide AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the workflow's authentication steps, `rad credential register` commands, and injection of session credentials into the Radius pods for Terraform provider access. - - From ab031d77d1619bb92637d2b25cabac402cb210a3 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Wed, 10 Jun 2026 14:46:21 -0500 Subject: [PATCH 06/17] Clarified the dependency on a frontend UI Signed-off-by: Zach Casper --- eng/design-notes/github/2026-06-repo-radius.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 4d248b52093..fe01b10de6c 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -15,16 +15,17 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Eliminate the prerequisite to have a Kubernetes cluster configured. * Enable Radius to run ephemerally within a GitHub Actions runner, starting on demand and shutting down after each operation. * Persist Radius state (application graphs, deployment history, environment configuration) in GitHub-native storage rather than in-cluster databases. -* Support multiple UX surfaces (Copilot app, CLI, browser extension, third-party integrations), with Copilot app integration as the top priority. +* Support the Copilot app integration in the near team and other UX surfaces in the future. * Maintain compatibility with the existing Radius resource model and Bicep/Terraform recipe system. ### Non-goals (out of scope) * Replacing the existing persistent control plane deployment model. Repo Radius is an alternative delivery mechanism, not a replacement. +* Building the frontend UI component such as the Copilot app. Repo Radius must ship with a separate frontend component. * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Multi-repository orchestration at the infrastructure layer. Multi-repo applications may use Repo Radius per-repo, but cross-repo state coordination is deferred. -* Building the Copilot agent, browser extension, Deployment panel UI, or other frontend components. These are separate components that consume Repo Radius capabilities. + ## User profile and challenges @@ -69,7 +70,7 @@ A developer who has outgrown Repo Radius can export their application definition ## Key dependencies and risks -* **Dependency: External UI**. Repo Radius is designed to be embedded in a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. Repo Radius must ship with a separate frontend component. +* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. * **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. From 0658811cefabbed520bf9a561b44c31f2b4082d6 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Wed, 17 Jun 2026 10:19:24 -0500 Subject: [PATCH 07/17] Grammar Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zach Casper --- eng/design-notes/github/2026-06-repo-radius.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index fe01b10de6c..192ec36f095 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -15,7 +15,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Eliminate the prerequisite to have a Kubernetes cluster configured. * Enable Radius to run ephemerally within a GitHub Actions runner, starting on demand and shutting down after each operation. * Persist Radius state (application graphs, deployment history, environment configuration) in GitHub-native storage rather than in-cluster databases. -* Support the Copilot app integration in the near team and other UX surfaces in the future. +* Support the Copilot app integration in the near term and other UX surfaces in the future. * Maintain compatibility with the existing Radius resource model and Bicep/Terraform recipe system. ### Non-goals (out of scope) From 0f01dabe990557c5cf799dcaf469dce9ac851932 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Mon, 29 Jun 2026 13:48:29 -0500 Subject: [PATCH 08/17] v2 Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 275 +++++++++++++----- 1 file changed, 205 insertions(+), 70 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 192ec36f095..5ff84389a02 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -16,7 +16,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Enable Radius to run ephemerally within a GitHub Actions runner, starting on demand and shutting down after each operation. * Persist Radius state (application graphs, deployment history, environment configuration) in GitHub-native storage rather than in-cluster databases. * Support the Copilot app integration in the near term and other UX surfaces in the future. -* Maintain compatibility with the existing Radius resource model and Bicep/Terraform recipe system. +* Reuse the existing self-hosted Radius implementation with minimal to no GitHub-specific modifications. ### Non-goals (out of scope) @@ -24,16 +24,18 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Building the frontend UI component such as the Copilot app. Repo Radius must ship with a separate frontend component. * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). -* Multi-repository orchestration at the infrastructure layer. Multi-repo applications may use Repo Radius per-repo, but cross-repo state coordination is deferred. +* Supporting multi-repo applications is deferred to a later date. +* Customizing the recipe pack is deferred to a later date. The initial release uses the default AWS and Azure recipe pack that ships with Radius. +* Creating mature, production-grade recipe packs for AWS and Azure. Repo Radius depends on the default recipe pack that ships with Radius; hardening and expanding that recipe pack is separate work outside the scope of Repo Radius. ## User profile and challenges ### User persona(s) -The primary user is a **developer** working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and may not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. +The primary user is a developer working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and likely do not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. -A secondary user is a **platform engineer** evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster, and later migrate to a self-hosted Radius installation if needed. +A secondary user is a platform engineer evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster and later migrate to a self-hosted Radius installation if needed. ### Challenge(s) faced by the user @@ -44,57 +46,51 @@ A secondary user is a **platform engineer** evaluating Radius. Repo Radius provi ### Positive user outcome -The developer, or a solution built on Repo Radius, creates the Radius workflow in the GitHub repository, then deploys their application to AWS or Azure without provisioning or managing any infrastructure beyond their cloud account. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. State is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. +A developer, using a solution built using Repo Radius, can deploy their application to AWS or Azure without having to install Kubernetes or Radius. They can provide their cloud account details and Repo Radius will provision the required infrastructure for their application. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. The state is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. ## Key scenarios -### Scenario 1: Deploy an application from a GitHub repository - -The developer asks to deploy their application. Cloud resources are provisioned on AWS or Azure without any prior Kubernetes cluster setup, Radius installation, or infrastructure configuration. - -### Scenario 2: Set up a new environment +### Scenario 1: Set up a new environment The developer creates an environment by providing cloud account details (e.g., AWS account ID and region, or Azure subscription and resource group). The environment is stored as a GitHub Environment and reused across subsequent deployments. -### Scenario 3: Authorize cloud access +### Scenario 2: Authorize cloud access The developer connects their AWS or Azure account to GitHub using OIDC identity federation so that deployments can provision cloud resources using short-lived tokens rather than stored credentials. +### Scenario 3: Deploy an application from a GitHub repository + +The developer asks to deploy their application. Cloud resources are provisioned on AWS or Azure without any prior Kubernetes cluster setup, Radius installation, or infrastructure configuration. + ### Scenario 4: Update a deployed application The developer modifies their application and deploys the change to an existing environment. Repo Radius loads the persisted state from the previous deployment and applies changes incrementally. -### Scenario 5: Migration to self-hosted Radius +### Scenario 5: Delete a deployed application -A developer who has outgrown Repo Radius can export their application definitions and environment configurations and migrate to a persistent Radius installation on Kubernetes. +The developer asks to delete their application. Repo Radius loads the persisted state, deletes the application and its associated cloud resources, and updates the persisted state to reflect the removal. -## Key dependencies and risks +### Scenario 6: Retrieve the application graph -* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. - -* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. +The developer asks to see the application graph for a deployed application including the resources it comprises, their IDs, and the connections between them. Repo Radius loads the persisted state and returns the JSON of the deployed application graph view without deploying or modifying any resources. -## Key assumptions to test and questions to answer +### Scenario 7: Run a `rad` CLI command -* **Reading application graph data**. Application graph data must be readable by the frontend without the Radius control plane being available and separate from the Radius control plane state. In the current designs for the application graph, graph data is stored separately from Repo Radius. +The developer (or the frontend on their behalf) needs an operation that does not have a dedicated action, such as listing resources or showing application details. Repo Radius runs the requested `rad` CLI command against the persisted state and returns its output. -* **Storage location**. What is the technical storage medium? The previous assumption used an orphan branch in the Git repository but that has security drawbacks. Are other options within GitHub, such as GHCR, sufficient for all state that Radius needs to persist, or will some state require a different storage mechanism? +### Scenario 8: Use an updated version of Repo Radius -* **Real-time deployment status**. How does the frontend receive real-time progress updates during a workflow run? The GitHub REST API does not support streaming job logs while a job is running. This feature spec includes the creation of artifacts after each Radius CLI command is executed, but GitHub Actions does not provide a public API for streaming updates (e.g., resource-by-resource updates from the `rad deploy` command). Is it possible, and if so, what mechanism should the workflow use to provide incremental status to the frontend? +Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/deploy@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag for breaking changes (`@v1`, `@v2` is the standard GitHub Actions semantic versioning convention). Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. -* **Concurrency**. How do we handle two developers triggering deployments to the same environment simultaneously? Git-based locking, GitHub environment protection rules, or something else? - -* **Credential lifecycle**. How are cloud provider credentials (OIDC tokens) scoped and rotated within the Repo Radius lifecycle? - -## Current state +## Key dependencies and risks -Work on many Repo Radius components or dependencies is already in flight. This includes: +* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. -* **Deploy to External AKS and EKS Clusters**: [Feature spec](https://github.com/radius-project/radius/blob/main/eng/design-notes/environments/2026-05-external-kubernetes.md) is reviewed and merged. +* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. -* **Externalize Radius state store**: The [technical design](https://github.com/radius-project/radius/pull/11457) is in draft but is expected to be superseded by a revised technical design. +* **Risk: Broad cloud permissions and blast radius**. The IAM role (AWS) or app registration (Azure) assumed via OIDC must hold a significant number of permissions to create and destroy the full range of resources an application may require. A role this broad has a high blast radius: a misbehaving recipe, a compromised workflow, or an over-scoped command could create, modify, or destroy a wide range of cloud resources. Scoping these permissions to the minimum necessary, and isolating them per environment, is an important mitigation to design for. -* **Workflow including OIDC authentication**: A [prototype workflow](https://github.com/radius-project/github-extension/blob/0378b1e349d09fe0f7f09c978c0e7a32c214a72a/.copilot/extensions/radius/src/shared/github-client.ts#L1581) has been implemented which includes OIDC authentication to AWS and Azure. This spec extends the prototype with (1) standardized workflow inputs and outputs and (2) loading of the external Radius state store. +* **Risk: Recipe and recipe pack backward compatibility**. Because Repo Radius is stateless and provisions on demand, every deployment re-resolves the application's resources against whatever recipe pack version is in effect at that time. Once an application is deployed, it must be assumed that there are live cloud resources backed by a recipe. Recipe and recipe pack versions must therefore be tightly controlled so that a newer recipe version remains backward-compatible with resources provisioned by an earlier version; a breaking recipe change could fail to reconcile, orphan, or inadvertently destroy resources that are already deployed. Recipe and recipe pack versioning needs the same backward-compatibility discipline as the action contract itself. ## Details of user problem @@ -108,89 +104,228 @@ As a result, I fall back to my existing infrastructure as code solution and do n After Repo Radius is implemented, a frontend can offer developers a complete deploy-to-cloud experience backed by Radius without requiring the developer to provision any infrastructure. The frontend handles user interaction and intent; Repo Radius handles execution. -### Detailed user experience +## Detailed user experience + +### Scenario 1-2: Set up a new environment and authorize cloud access + +#### Step 1: Enable Repo Radius for the repository + +A workflow file in the repository's `.github/workflows/` directory is required for GitHub Actions to run; an action cannot be invoked without one. The frontend therefore writes one or more lightweight workflows to the repository on the user's behalf. These workflows are intentionally thin: they contain no Repo Radius logic and simply invoke the published Repo Radius GitHub Actions. All implementation detail lives within the published actions, so a committed workflow only needs to change when the repository adopts a new major version of an action (see Scenario 8). + +Repo Radius is published as the following GitHub Actions, each pinned to a matching major version (e.g., `@v1`): -Repo Radius is a backend designed to be driven by a separate frontend component (Copilot app, CLI, browser extension, or other integration). The frontend interacts with Repo Radius entirely through GitHub APIs. The following steps describe the interaction model. +| Workflow | Action | Purpose | +| --- | --- | --- | +| `radius-verify-cloud-auth.yml` | `radius-project/verify-cloud-auth` | Verify that the GitHub Environment's OIDC configuration can authenticate to the cloud provider before attempting a deployment. | +| `radius-deploy.yml` | `radius-project/deploy` | Deploy or update an application to the environment. | +| `radius-destroy.yml` | `radius-project/destroy` | Delete an application and its associated cloud resources from the environment. | +| `radius-app-graph.yml` | `radius-project/app-graph` | Retrieve the application graph, including the resources an application comprises and the connections between them. This is a read-only operation. | +| `radius-run-rad-command.yml` | `radius-project/run-rad-command` | Execute arbitrary `rad` CLI commands. This is the general-purpose primitive for any operation that does not have a dedicated action. | -#### Step 1: Environment setup (prerequisite) +#### Step 2: Environment setup The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKS or AKS cluster name and Kubernetes namespace) for application workloads. -#### Step 2: OIDC setup (prerequisite) +The environment stores the following cloud provider and cluster details: + +| Variable | Cloud | Required | Description | +| --- | --- | --- | --- | +| `AWS_ACCOUNT_ID` | AWS | Yes (AWS) | AWS account ID where cloud resources are provisioned. | +| `AWS_EKS_CLUSTER_NAME` | AWS | Yes (AWS) | Name of the target EKS cluster for application workloads. | +| `AWS_REGION` | AWS | Yes (AWS) | AWS region where cloud resources are provisioned. | +| `AZURE_AKS_CLUSTER_NAME` | Azure | Yes (Azure) | Name of the target AKS cluster for application workloads. | +| `AZURE_RESOURCE_GROUP` | Azure | Yes (Azure) | Resource group for provisioned cloud resources. | +| `AZURE_SUBSCRIPTION_ID` | Azure | Yes (Azure) | Subscription ID where cloud resources are provisioned. | +| `KUBERNETES_NAMESPACE` | Both | No | Kubernetes namespace for application workloads. Defaults to `default` if not specified. | + +While the frontend typically creates the GitHub Environment on the user's behalf, the user can also create it directly using the `gh` CLI or the GitHub REST API. For example, using the `gh` CLI to create `dev`, `test`, and `prod` environments and set their variables: + +```bash +for env in dev test prod; do + # Create the environment + gh api --method PUT repos/{owner}/{repo}/environments/$env + + # Set the cloud provider and cluster variables + gh variable set AWS_ACCOUNT_ID --env $env --body "123456789012" + gh variable set AWS_REGION --env $env --body "us-west-2" + gh variable set AWS_EKS_CLUSTER_NAME --env $env --body "my-eks-cluster" + gh variable set KUBERNETES_NAMESPACE --env $env --body "my-app-$env" +done +``` + +If GitHub offers a more declarative approach to creating and configuring environments (for example, a configuration file checked into the repository), that approach can be used instead of the imperative `gh` CLI commands shown above. + +#### Step 3: OIDC setup The frontend guides the user through configuring their cloud provider to trust the GitHub Actions OIDC identity provider as a federated identity source, so that the workflow can authenticate using short-lived tokens rather than stored credentials. For Azure, this means creating a federated credential on an Entra ID app registration that trusts tokens issued by GitHub Actions; the resulting client ID, tenant ID, and subscription ID are stored in the GitHub Environment. For AWS, this means creating an IAM OIDC identity provider and an IAM role with a trust policy that accepts GitHub Actions tokens; the role ARN and region are stored in the GitHub Environment. -#### Step 3: Workflow dispatch +The environment stores the following authentication details: + +| Variable | Cloud | Required | Description | +| --- | --- | --- | --- | +| `AZURE_CLIENT_ID` | Azure | Yes (Azure) | Client ID of the Entra ID app registration used for OIDC federation. | +| `AZURE_TENANT_ID` | Azure | Yes (Azure) | Entra ID tenant ID. | +| `AWS_ROLE_ARN` | AWS | Yes (AWS) | ARN of the IAM role assumed via OIDC federation. | + +#### Step 4: Verify cloud authentication + +Before the environment is considered ready, the frontend dispatches the `radius-verify-cloud-auth.yml` workflow (which invokes the `radius-project/verify-cloud-auth` action) for the target GitHub Environment. This confirms that the environment's variables and OIDC configuration are complete and correct, so that misconfiguration is surfaced during setup rather than on the first deployment. + +The verification performs an end-to-end authentication check: it requests a GitHub Actions OIDC token, exchanges it with the cloud provider, and confirms that the resulting short-lived credentials are valid. It does not provision, modify, or delete any resources. + +When an EKS or AKS cluster name is provided in the environment, the verification additionally confirms Kubernetes connectivity: it uses the authenticated cloud credentials to retrieve the cluster's access configuration and confirms that the cluster's API server is reachable. This catches a misnamed cluster, a cluster in a different region or resource group, or an identity that lacks access to the cluster before the first deployment. + +The outcome is reported in two complementary ways, consistent with how all Repo Radius workflows return results: -The frontend calls the GitHub API `workflow_dispatch` to initiate a Repo Radius workflow. The workflow accepts the following inputs: +* **Workflow run conclusion**. The GitHub Actions run concludes as success or failure, giving the frontend a coarse pass/fail signal it can read directly from the run status via the GitHub Actions API without downloading any files. +* **Result artifact**. The workflow uploads a structured result artifact (a small JSON document) containing the specific outcome from the table below, a human-readable message, and remediation guidance. The frontend downloads this artifact to distinguish between the failure modes (for example, "trust not established" versus "cluster unreachable") and to surface actionable guidance to the user. + +The workflow reports one of the following outcomes: + +| Outcome | Meaning | Action Exit Code | Example artifact contents | +| --- | --- | --- | --- | +| Success | Authentication succeeded and the credentials are valid. | 0 | `{"outcome": "success", "message": "Authentication succeeded. Environment is ready for deployments."}` | +| Missing configuration | A required variable for the selected cloud provider is absent. | 1 | `{"outcome": "missing_configuration", "message": "Required variable AWS_ROLE_ARN is not set. Re-run environment and OIDC setup to populate the environment."}` | +| Trust not established | The cloud provider rejected the GitHub OIDC token. | 2 | `{"outcome": "trust_not_established", "message": "Cloud provider rejected the GitHub OIDC token. Re-run environment and OIDC setup to correct or verify the IAM role trust policy or app registration federated credential matches the repository, environment, and subject claim."}` | +| Insufficient permissions | Authentication succeeded but the identity lacks permission to perform the check. | 3 | `{"outcome": "insufficient_permissions", "message": "Authenticated successfully, but the AWS IAM role or Azure app registration is missing the required permissions. Re-run the environment and OIDC setup or manually add the missing permission. See the Radius documentation for required permissions."}` | +| Cluster unreachable | A cluster name was provided, but the cluster could not be found or its API server could not be reached. | 4 | `{"outcome": "cluster_unreachable", "message": "Cluster my-eks-cluster could not be found or its API server is unreachable. Correct the EKS cluster name, or grant the identity access to the cluster."}` | +| Provider unreachable | The cloud provider's authentication endpoint could not be reached. | 5 | `{"outcome": "provider_unreachable", "message": "Cloud provider authentication endpoint could not be reached. Re-run the verification workflow to try again. If it persists, check the provider's service status."}` | + +The result artifact is uploaded under a well-known, stable artifact name (for example, `verify-result`) and is available to the frontend via the GitHub Actions API. The artifact name and the JSON schema (the set of fields and the allowed `outcome` values) form part of the stable contract between Repo Radius and any frontend, so they must remain backward-compatible within a major version. + +### Scenario 3-4: Deploy a new or updated application + +The frontend deploys an application by dispatching the `radius-deploy.yml` workflow (which invokes the `radius-project/deploy` action) for the target GitHub Environment. The workflow accepts the following inputs: | Input | Required | Description | | --- | --- | --- | | `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `radius_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command, to be executed in order, with the `rad` prefix omitted. | +| `app_definition` | Yes | Path, relative to the repository root, to the application definition (for example, a Bicep file) to deploy. | -**Single command:** +Example dispatch: ```json -POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches +POST /repos/{owner}/{repo}/actions/workflows/radius-deploy.yml/dispatches { "ref": "main", "inputs": { "environment": "production", - "radius_commands": "deploy app.bicep" + "app_definition": ".radius/app.bicep" } } ``` -**Multiple commands:** +The `radius-deploy.yml` workflow contains only one action calling the `radius-project/deploy@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/deploy` action performs the following: -```json -POST /repos/{owner}/{repo}/actions/workflows/radius/dispatches -{ - "ref": "main", - "inputs": { - "environment": "production", - "radius_commands": "[\"deploy app.bicep\", \"app graph\"]" - } -} -``` +1. **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. + +1. **Load the Radius data store**. The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. + +1. **Create an ephemeral Radius control plane**. The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. + +1. **Create a Radius environment based on the GitHub Environment**. The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. + +1. **Register the default recipe pack**. The workflow registers the default AWS and Azure recipe pack that ships with Radius to the environment, so the application's resources resolve to recipes without the user having to author or register recipes themselves. Customizing the recipe pack is a future feature; the initial release uses the default recipe pack only. + +1. **Execute `rad deploy`**. The workflow runs `rad deploy "${{ inputs.app_definition }}"` then uploads the output as a workflow artifact. The workflow continues even if the deployment fails. + +1. **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. + +The workflow reports one of the following outcomes: + +| Outcome | Meaning | Action Exit Code | Example artifact contents | +| --- | --- | --- | --- | +| Deployment succeeded | The deployment succeeded. | 0 | A JSON representation of the application graph similar to the output from `rad app graph -o json`. | +| Deployment failed | One or more resources failed to deploy. | 1 | The output of `rad deploy` including the error message. | + +### Scenario 5: Delete a deployed application + +The frontend deletes an application by dispatching the `radius-destroy.yml` workflow (which invokes the `radius-project/destroy` action) for the target GitHub Environment. The workflow accepts the following inputs: + +| Input | Required | Description | +| --- | --- | --- | +| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | +| `application` | Yes | Name of the application to delete. | + +The `radius-destroy.yml` workflow contains only one action calling the `radius-project/destroy@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/destroy` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs `rad app delete "${{ inputs.application }}" --yes` to delete the application and its associated cloud resources. The updated data store is then persisted so the removal is reflected in subsequent runs. -#### Step 4: Workflow execution +The workflow reports one of the following outcomes: -The single Repo Radius workflow is executed within a GitHub Actions runner. A prototype of this workflow has been validated. The workflow performs the following stages: +| Outcome | Meaning | Action Exit Code | Example artifact contents | +| --- | --- | --- | --- | +| Deletion succeeded | The application and its associated cloud resources were deleted. | 0 | The output of `rad app delete` confirming the application was deleted. | +| Deletion failed | One or more resources failed to delete. | 1 | The output of `rad app delete` including the error message. | -* **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. +### Scenario 6: Retrieve the application graph -* **Load the Radius data store**. The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. +The frontend retrieves the application graph for a deployed application by dispatching the `radius-app-graph.yml` workflow (which invokes the `radius-project/app-graph` action) for the target GitHub Environment. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources. The workflow accepts the following inputs: -* **Create an ephemeral Radius control plane**. The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. +| Input | Required | Description | +| --- | --- | --- | +| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | +| `application` | Yes | Name of the application whose graph is retrieved. | + +The `radius-app-graph.yml` workflow contains only one action calling the `radius-project/app-graph@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/app-graph` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs `rad app graph "${{ inputs.application }}" -o json` and uploads the result. Because the operation only reads data, nothing is created, modified, or deleted, and the data store is unchanged. + +The workflow reports one of the following outcomes: -* **Configure cloud provider credentials**. Cloud credentials from the OIDC exchange are injected into the Radius control plane via the standard `rad credential register` commands. +| Outcome | Meaning | Action Exit Code | Example artifact contents | +| --- | --- | --- | --- | +| Graph retrieved | The application graph was retrieved successfully. | 0 | The JSON of the deployed application graph view, as produced by `rad app graph -o json`. | +| Retrieval failed | The application graph could not be retrieved (for example, the application does not exist). | 1 | The output of `rad app graph` including the error message. | -* **Create a Radius environment based on the GitHub Environment**. The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. +### Scenario 7: Run a `rad` CLI command -* **Execute `rad` CLI commands**. The workflow iterates over the `radius_commands` input. For each command, the workflow executes the command, captures its output, and uploads the output as a workflow artifact. If a command fails, the workflow stops iteration and proceeds to persist the data store. +Unlike the deploy, destroy, and app-graph scenarios, which use purpose-built actions, this scenario uses `radius-project/run-rad-command`, the catch-all action that can run any `rad` CLI command. Any operation Radius supports but does not have a dedicated action for, such as `rad resource list` or `rad app show`, is performed through the `radius-project/run-rad-command` action. + +The frontend dispatches the `radius-run-rad-command.yml` workflow (which invokes the `radius-project/run-rad-command` action) for the target GitHub Environment. The workflow accepts the following inputs: + +| Input | Required | Description | +| --- | --- | --- | +| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | +| `rad_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command, run in order, with the `rad` prefix omitted (for example, `resource list -o json`). | -* **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. +The `radius-run-rad-command.yml` workflow contains only one action calling the `radius-project/run-rad-command@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/run-rad-command` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs each command supplied in the `rad_commands` input. When the commands only read data, nothing is created, modified, or deleted, and the data store is unchanged; commands that change state persist the data store as usual. -#### Step 5: Read workflow output +Each command produces its own artifact, named by its zero-based position in the `rad_commands` array: `rad-command-0`, `rad-command-1`, and so on. The frontend correlates each artifact to the command at the same index. Commands run in order, and each command's artifact is uploaded as soon as that command completes, so the frontend can retrieve results incrementally while later commands are still running. If a command fails, the action stops and does not run the remaining commands; only the artifacts for the commands that ran are produced. -The frontend reads the output of the workflow by polling the GitHub Actions API for the workflow run status and downloading artifacts as they become available. Artifacts are available via the API as soon as each upload step completes, so the frontend can retrieve results incrementally while the workflow is still running. +The workflow reports one of the following outcomes. The overall workflow run concludes as success only if every command succeeded; if any command fails, the run concludes as failure. + +| Outcome | Meaning | Action Exit Code | Example artifact contents | +| --- | --- | --- | --- | +| Command succeeded | The command ran successfully. | 0 | Per command, an artifact named `rad-command-` containing the command's output, such as the JSON resource list from `rad resource list -o json`. | +| Command failed | A command did not complete successfully; remaining commands are not run. | 1 | The `rad-command-` artifact for the failed command containing its output including the error message. | + +### Scenario 8: Use an updated version of Repo Radius + +The five Repo Radius actions are versioned independently and published to the GitHub Marketplace. The lightweight workflows committed to the user's repository reference each action by its major version tag (for example, `radius-project/deploy@v1`), so every dispatch automatically picks up the latest backward-compatible release of that major version without the user changing anything. + +The `@v1` actions are expected to be long-lasting. Bug fixes and backward-compatible enhancements ship as new `v1.x` releases under the same `@v1` tag, so the user receives them transparently on the next workflow run. Because there is no persistent control plane, there is nothing for the user to upgrade, monitor, or migrate between releases. + +When a change would break the established contract (for example, the workflow inputs, the result artifact names, or the JSON schema), a new major version of the affected action is published (`@v2`). The existing `@v1` action continues to work unchanged, so existing repositories are unaffected until they opt in. To adopt the new version, the frontend updates the workflow in the repository to reference the new major tag (for example, `radius-project/deploy@v2`). This is the only situation in which a committed workflow needs to change. ## Key investments -### Investment 1: Deployment to external Kubernetes cluster +### Investment 1: Deployment to an external Kubernetes cluster + +Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying workloads to the developer's target cluster, so the control plane and workload clusters must be fully decoupled. This capability is delivered by the [Deploy to External AKS and EKS Clusters](../environments/2026-05-external-kubernetes.md) feature, which names the external cluster on the environment's cloud provider block (`aws.eksClusterName` or `azure.aksClusterName`) and acquires Kubernetes API access on demand using the AWS or Azure credentials already registered with Radius. Repo Radius depends on that feature and reuses its environment shape and on-demand credential acquisition rather than introducing a separate mechanism. + +### Investment 2: Externalized, portable Radius data store + +Move the Radius data store out of the in-cluster database so that state persists across otherwise stateless runs. At the end of a run the store is exported to GitHub-native storage, and at the start of the next run it is re-imported into the ephemeral control plane. This covers the Radius resource data, application graphs, deployment history, and Terraform state. + +### Investment 3: Repo Radius GitHub Actions with a stable contract -Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. This is required because Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying to the developer's target cluster. +Build and publish the five Repo Radius actions (`verify-cloud-auth`, `deploy`, `destroy`, `app-graph`, and `run-rad-command`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema. This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward-compatible within a major version. -### Investment 2: Externalization of the Radius data store +### Investment 4: Lightweight workflows for each action -Move the Radius data store out of the ephemeral Kubernetes cluster so that state persists across workflow runs. This includes the Radius resource data, application graphs, deployment history, and Terraform state. +Provide the thin, ready-to-commit workflow file that pairs with each action (`radius-verify-cloud-auth.yml`, `radius-deploy.yml`, `radius-destroy.yml`, `radius-app-graph.yml`, and `radius-run-rad-command.yml`). Each workflow declares its inputs and invokes only the corresponding action at its major version tag, with no Repo Radius logic of its own. The frontend writes these workflows into the user's repository so that GitHub Actions has a dispatchable entry point for each operation, and because the workflows contain no logic they only need to change when the referenced major version is updated. -### Investment 3: Repo Radius workflow with standardized inputs and outputs +### Investment 5: Fast ephemeral control plane startup -Build the single reusable GitHub Actions workflow that orchestrates Repo Radius operations. The workflow accepts a GitHub Environment name and `rad` CLI commands as inputs, and produces artifacts as outputs. The input and output contract must be stable and well-defined so that multiple frontend components can drive Repo Radius without coupling to its internals. +Provision the k3d cluster, install the `rad` CLI, and install Radius quickly enough that the per-run startup cost is acceptable within a GitHub Actions runner. Because the control plane is created and torn down on every operation, startup time is on the critical path for every user-facing action and is the primary determinant of perceived responsiveness. -### Investment 4: Cloud credential integration +### Investment 6: Cloud credential integration via OIDC -Integrate with GitHub's OIDC federation to securely provide AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the workflow's authentication steps, `rad credential register` commands, and injection of session credentials into the Radius pods for Terraform provider access. +Integrate with GitHub's OIDC federation to provide short-lived AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the per-run authentication steps, injection of session credentials into the Radius pods for Terraform provider access, and the `verify-cloud-auth` preflight check that validates the environment's configuration before the first deployment. \ No newline at end of file From f695f59ca9cf082a173dcf20a53dbe4ff16e9a0d Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Mon, 29 Jun 2026 15:28:54 -0500 Subject: [PATCH 09/17] Reverted to a single run-rad-commands actions Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 136 ++++++++---------- 1 file changed, 58 insertions(+), 78 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 5ff84389a02..81213a018e3 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -28,7 +28,6 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Customizing the recipe pack is deferred to a later date. The initial release uses the default AWS and Azure recipe pack that ships with Radius. * Creating mature, production-grade recipe packs for AWS and Azure. Repo Radius depends on the default recipe pack that ships with Radius; hardening and expanding that recipe pack is separate work outside the scope of Repo Radius. - ## User profile and challenges ### User persona(s) @@ -74,13 +73,9 @@ The developer asks to delete their application. Repo Radius loads the persisted The developer asks to see the application graph for a deployed application including the resources it comprises, their IDs, and the connections between them. Repo Radius loads the persisted state and returns the JSON of the deployed application graph view without deploying or modifying any resources. -### Scenario 7: Run a `rad` CLI command - -The developer (or the frontend on their behalf) needs an operation that does not have a dedicated action, such as listing resources or showing application details. Repo Radius runs the requested `rad` CLI command against the persisted state and returns its output. - -### Scenario 8: Use an updated version of Repo Radius +### Scenario 7: Use an updated version of Repo Radius -Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/deploy@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag for breaking changes (`@v1`, `@v2` is the standard GitHub Actions semantic versioning convention). Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. +Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/run-rad-commands@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag for breaking changes (`@v1`, `@v2` is the standard GitHub Actions semantic versioning convention). Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. ## Key dependencies and risks @@ -110,17 +105,14 @@ After Repo Radius is implemented, a frontend can offer developers a complete dep #### Step 1: Enable Repo Radius for the repository -A workflow file in the repository's `.github/workflows/` directory is required for GitHub Actions to run; an action cannot be invoked without one. The frontend therefore writes one or more lightweight workflows to the repository on the user's behalf. These workflows are intentionally thin: they contain no Repo Radius logic and simply invoke the published Repo Radius GitHub Actions. All implementation detail lives within the published actions, so a committed workflow only needs to change when the repository adopts a new major version of an action (see Scenario 8). +A workflow file in the repository's `.github/workflows/` directory is required for GitHub Actions to run; an action cannot be invoked without one. The frontend therefore writes the two lightweight workflows to the repository on the user's behalf: one to verify cloud authentication and one to run `rad` commands. These workflows are intentionally thin: they contain no Repo Radius logic and simply invoke the published Repo Radius GitHub Actions. All implementation detail lives within the published actions, so a committed workflow only needs to change when the repository adopts a new major version of an action (see Scenario 7). Repo Radius is published as the following GitHub Actions, each pinned to a matching major version (e.g., `@v1`): | Workflow | Action | Purpose | | --- | --- | --- | | `radius-verify-cloud-auth.yml` | `radius-project/verify-cloud-auth` | Verify that the GitHub Environment's OIDC configuration can authenticate to the cloud provider before attempting a deployment. | -| `radius-deploy.yml` | `radius-project/deploy` | Deploy or update an application to the environment. | -| `radius-destroy.yml` | `radius-project/destroy` | Delete an application and its associated cloud resources from the environment. | -| `radius-app-graph.yml` | `radius-project/app-graph` | Retrieve the application graph, including the resources an application comprises and the connections between them. This is a read-only operation. | -| `radius-run-rad-command.yml` | `radius-project/run-rad-command` | Execute arbitrary `rad` CLI commands. This is the general-purpose primitive for any operation that does not have a dedicated action. | +| `radius-run-rad-commands.yml` | `radius-project/run-rad-commands` | Run one or more `rad` CLI commands against the environment. This single action performs every Radius operation (deploying, updating, deleting, and reading) by running the corresponding `rad` commands. | #### Step 2: Environment setup @@ -195,27 +187,30 @@ The result artifact is uploaded under a well-known, stable artifact name (for ex ### Scenario 3-4: Deploy a new or updated application -The frontend deploys an application by dispatching the `radius-deploy.yml` workflow (which invokes the `radius-project/deploy` action) for the target GitHub Environment. The workflow accepts the following inputs: +The frontend deploys (or updates) an application by dispatching the `radius-run-rad-commands.yml` workflow (which invokes the `radius-project/run-rad-commands` action) for the target GitHub Environment. Deployment is expressed as a `rad deploy` command in the `rad_commands` input; an update uses the same command against the same environment, and Radius reconciles the change incrementally from the persisted state. The workflow accepts the following inputs: -| Input | Required | Description | -| --- | --- | --- | -| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `app_definition` | Yes | Path, relative to the repository root, to the application definition (for example, a Bicep file) to deploy. | +| Input | Value | +| --- | --- | +| `environment` | The GitHub Environment name | +| `rad_commands` | `['deploy .radius/app.bicep', 'app graph my-app -o json']` | + +> [!IMPORTANT] +> Repo Radius does not accept arbitrary `rad` invocations. Each entry in `rad_commands` is validated against an enum of allowed commands so that commands which are not applicable to the ephemeral, per-run model (for example, commands that manage or upgrade a persistent control plane, switch workspaces, or change installation state) cannot run, and so the contract stays narrow and reviewable. Validation happens before the control plane is created or any command runs, so a disallowed command errors out and fails fast without provisioning anything. The specific set of allowed commands is defined in the technical design. The enum is part of the stable contract and is versioned with the action; additional commands may be introduced in backward-compatible releases. -Example dispatch: +Example dispatch. The frontend deploys the application with `rad deploy`, then captures the resulting application graph with `rad app graph`: ```json -POST /repos/{owner}/{repo}/actions/workflows/radius-deploy.yml/dispatches +POST /repos/{owner}/{repo}/actions/workflows/radius-run-rad-commands.yml/dispatches { "ref": "main", "inputs": { "environment": "production", - "app_definition": ".radius/app.bicep" + "rad_commands": "[\"deploy .radius/app.bicep\", \"app graph my-app -o json\"]" } } ``` -The `radius-deploy.yml` workflow contains only one action calling the `radius-project/deploy@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/deploy` action performs the following: +The `radius-run-rad-commands.yml` workflow contains only one action calling the `radius-project/run-rad-commands@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/run-rad-commands` action performs the following: 1. **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. @@ -227,7 +222,7 @@ The `radius-deploy.yml` workflow contains only one action calling the `radius-pr 1. **Register the default recipe pack**. The workflow registers the default AWS and Azure recipe pack that ships with Radius to the environment, so the application's resources resolve to recipes without the user having to author or register recipes themselves. Customizing the recipe pack is a future feature; the initial release uses the default recipe pack only. -1. **Execute `rad deploy`**. The workflow runs `rad deploy "${{ inputs.app_definition }}"` then uploads the output as a workflow artifact. The workflow continues even if the deployment fails. +1. **Execute the `rad` commands**. The workflow runs each command supplied in the `rad_commands` input, in order, prefixing each with `rad`, and captures each command's output. If a command fails, the action stops and does not run the remaining commands. 1. **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. @@ -235,74 +230,39 @@ The workflow reports one of the following outcomes: | Outcome | Meaning | Action Exit Code | Example artifact contents | | --- | --- | --- | --- | -| Deployment succeeded | The deployment succeeded. | 0 | A JSON representation of the application graph similar to the output from `rad app graph -o json`. | -| Deployment failed | One or more resources failed to deploy. | 1 | The output of `rad deploy` including the error message. | +| All commands succeeded | Every command ran successfully. | 0 | The `rad-commands-result` artifact whose `commands` array has an entry per command, each with the command and its output (such as the `rad deploy` output and the JSON application graph). | +| A command failed | A command did not complete successfully; remaining commands are not run. | 1 | The `rad-commands-result` artifact whose last entry is the failed command including its exit code and error output. Entries for commands that did not run are absent. | +| Disallowed command | One or more commands are not in the allowed-command enum; the request is rejected and no command runs. | 2 | The `rad-commands-result` artifact identifying the disallowed command. | ### Scenario 5: Delete a deployed application -The frontend deletes an application by dispatching the `radius-destroy.yml` workflow (which invokes the `radius-project/destroy` action) for the target GitHub Environment. The workflow accepts the following inputs: +The frontend deletes an application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app delete` command in the `rad_commands` input. The workflow accepts the same inputs as the deploy scenario: -| Input | Required | Description | -| --- | --- | --- | -| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `application` | Yes | Name of the application to delete. | - -The `radius-destroy.yml` workflow contains only one action calling the `radius-project/destroy@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/destroy` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs `rad app delete "${{ inputs.application }}" --yes` to delete the application and its associated cloud resources. The updated data store is then persisted so the removal is reflected in subsequent runs. +| Input | Value | +| --- | --- | +| `environment` | The GitHub Environment name | +| `rad_commands` | `['app delete my-app --yes']` | -The workflow reports one of the following outcomes: - -| Outcome | Meaning | Action Exit Code | Example artifact contents | -| --- | --- | --- | --- | -| Deletion succeeded | The application and its associated cloud resources were deleted. | 0 | The output of `rad app delete` confirming the application was deleted. | -| Deletion failed | One or more resources failed to delete. | 1 | The output of `rad app delete` including the error message. | +The action follows the same stages as the deploy scenario, running `rad app delete "" --yes` to delete the application and its associated cloud resources. The updated data store is then persisted so the removal is reflected in subsequent runs. The command's output is included in the single `rad-commands-result` artifact, and the workflow reports the same outcomes described in the deploy scenario. ### Scenario 6: Retrieve the application graph -The frontend retrieves the application graph for a deployed application by dispatching the `radius-app-graph.yml` workflow (which invokes the `radius-project/app-graph` action) for the target GitHub Environment. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources. The workflow accepts the following inputs: +The frontend retrieves the application graph for a deployed application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app graph` command in the `rad_commands` input. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources, so the data store is unchanged. -| Input | Required | Description | -| --- | --- | --- | -| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `application` | Yes | Name of the application whose graph is retrieved. | +| Input | Value | +| --- | --- | +| `environment` | The GitHub Environment name | +| `rad_commands` | `['app graph my-app -o json']` | -The `radius-app-graph.yml` workflow contains only one action calling the `radius-project/app-graph@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/app-graph` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs `rad app graph "${{ inputs.application }}" -o json` and uploads the result. Because the operation only reads data, nothing is created, modified, or deleted, and the data store is unchanged. +The action runs `rad app graph "" -o json` and writes the result to the single `rad-commands-result` artifact. The workflow reports the same outcomes described in the deploy scenario; on success, the artifact's single entry contains the JSON of the deployed application graph view. -The workflow reports one of the following outcomes: +### Scenario 7: Use an updated version of Repo Radius -| Outcome | Meaning | Action Exit Code | Example artifact contents | -| --- | --- | --- | --- | -| Graph retrieved | The application graph was retrieved successfully. | 0 | The JSON of the deployed application graph view, as produced by `rad app graph -o json`. | -| Retrieval failed | The application graph could not be retrieved (for example, the application does not exist). | 1 | The output of `rad app graph` including the error message. | - -### Scenario 7: Run a `rad` CLI command - -Unlike the deploy, destroy, and app-graph scenarios, which use purpose-built actions, this scenario uses `radius-project/run-rad-command`, the catch-all action that can run any `rad` CLI command. Any operation Radius supports but does not have a dedicated action for, such as `rad resource list` or `rad app show`, is performed through the `radius-project/run-rad-command` action. - -The frontend dispatches the `radius-run-rad-command.yml` workflow (which invokes the `radius-project/run-rad-command` action) for the target GitHub Environment. The workflow accepts the following inputs: - -| Input | Required | Description | -| --- | --- | --- | -| `environment` | Yes | The GitHub Environment name to use as the Radius environment. | -| `rad_commands` | Yes | A single `rad` CLI command string or a JSON-encoded array of strings. Each string is a `rad` CLI command, run in order, with the `rad` prefix omitted (for example, `resource list -o json`). | - -The `radius-run-rad-command.yml` workflow contains only one action calling the `radius-project/run-rad-command@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/run-rad-command` action follows the same stages as the `radius-project/deploy` action, except that instead of running `rad deploy` it runs each command supplied in the `rad_commands` input. When the commands only read data, nothing is created, modified, or deleted, and the data store is unchanged; commands that change state persist the data store as usual. - -Each command produces its own artifact, named by its zero-based position in the `rad_commands` array: `rad-command-0`, `rad-command-1`, and so on. The frontend correlates each artifact to the command at the same index. Commands run in order, and each command's artifact is uploaded as soon as that command completes, so the frontend can retrieve results incrementally while later commands are still running. If a command fails, the action stops and does not run the remaining commands; only the artifacts for the commands that ran are produced. - -The workflow reports one of the following outcomes. The overall workflow run concludes as success only if every command succeeded; if any command fails, the run concludes as failure. - -| Outcome | Meaning | Action Exit Code | Example artifact contents | -| --- | --- | --- | --- | -| Command succeeded | The command ran successfully. | 0 | Per command, an artifact named `rad-command-` containing the command's output, such as the JSON resource list from `rad resource list -o json`. | -| Command failed | A command did not complete successfully; remaining commands are not run. | 1 | The `rad-command-` artifact for the failed command containing its output including the error message. | - -### Scenario 8: Use an updated version of Repo Radius - -The five Repo Radius actions are versioned independently and published to the GitHub Marketplace. The lightweight workflows committed to the user's repository reference each action by its major version tag (for example, `radius-project/deploy@v1`), so every dispatch automatically picks up the latest backward-compatible release of that major version without the user changing anything. +The two Repo Radius actions are versioned independently and published to the GitHub Marketplace. The lightweight workflows committed to the user's repository reference each action by its major version tag (for example, `radius-project/run-rad-commands@v1`), so every dispatch automatically picks up the latest backward-compatible release of that major version without the user changing anything. The `@v1` actions are expected to be long-lasting. Bug fixes and backward-compatible enhancements ship as new `v1.x` releases under the same `@v1` tag, so the user receives them transparently on the next workflow run. Because there is no persistent control plane, there is nothing for the user to upgrade, monitor, or migrate between releases. -When a change would break the established contract (for example, the workflow inputs, the result artifact names, or the JSON schema), a new major version of the affected action is published (`@v2`). The existing `@v1` action continues to work unchanged, so existing repositories are unaffected until they opt in. To adopt the new version, the frontend updates the workflow in the repository to reference the new major tag (for example, `radius-project/deploy@v2`). This is the only situation in which a committed workflow needs to change. +When a change would break the established contract (for example, the workflow inputs, the allowed-command set, the result artifact names, or the JSON schema), a new major version of the affected action is published (`@v2`). The existing `@v1` action continues to work unchanged, so existing repositories are unaffected until they opt in. To adopt the new version, the frontend updates the workflow in the repository to reference the new major tag (for example, `radius-project/run-rad-commands@v2`). This is the only situation in which a committed workflow needs to change. ## Key investments @@ -316,11 +276,11 @@ Move the Radius data store out of the in-cluster database so that state persists ### Investment 3: Repo Radius GitHub Actions with a stable contract -Build and publish the five Repo Radius actions (`verify-cloud-auth`, `deploy`, `destroy`, `app-graph`, and `run-rad-command`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema. This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward-compatible within a major version. +Build and publish the two Repo Radius actions (`verify-cloud-auth` and `run-rad-commands`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema. This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward-compatible within a major version. ### Investment 4: Lightweight workflows for each action -Provide the thin, ready-to-commit workflow file that pairs with each action (`radius-verify-cloud-auth.yml`, `radius-deploy.yml`, `radius-destroy.yml`, `radius-app-graph.yml`, and `radius-run-rad-command.yml`). Each workflow declares its inputs and invokes only the corresponding action at its major version tag, with no Repo Radius logic of its own. The frontend writes these workflows into the user's repository so that GitHub Actions has a dispatchable entry point for each operation, and because the workflows contain no logic they only need to change when the referenced major version is updated. +Provide the thin, ready-to-commit workflow file that pairs with each action (`radius-verify-cloud-auth.yml` and `radius-run-rad-commands.yml`). Each workflow declares its inputs and invokes only the corresponding action at its major version tag, with no Repo Radius logic of its own. The frontend writes these workflows into the user's repository so that GitHub Actions has a dispatchable entry point for each operation, and because the workflows contain no logic they only need to change when the referenced major version is updated. ### Investment 5: Fast ephemeral control plane startup @@ -328,4 +288,24 @@ Provision the k3d cluster, install the `rad` CLI, and install Radius quickly eno ### Investment 6: Cloud credential integration via OIDC -Integrate with GitHub's OIDC federation to provide short-lived AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the per-run authentication steps, injection of session credentials into the Radius pods for Terraform provider access, and the `verify-cloud-auth` preflight check that validates the environment's configuration before the first deployment. \ No newline at end of file +Integrate with GitHub's OIDC federation to provide short-lived AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the per-run authentication steps, injection of session credentials into the Radius pods for Terraform provider access, and the `verify-cloud-auth` preflight check that validates the environment's configuration before the first deployment. + +## Alternatives considered + +### Multiple operation-specific actions versus a single generic action + +We considered publishing a separate action for each operation (for example, `radius-deploy`, `radius-destroy`, `radius-app-graph`) instead of the single `run-rad-commands` action. + +* **Multiple operation-specific actions**. Each action exposes a narrow, strongly typed input surface (for example, `radius-deploy` takes an application path; `radius-destroy` takes an application name), so the contract is self-documenting and each action validates only the inputs relevant to it. The drawback is that every new operation requires a new action, a new workflow file committed to the repository, and a new major-version lifecycle to maintain. Composing several operations in one run (deploy, then read the graph) requires multiple workflow dispatches and multiple ephemeral control plane startups, multiplying the per-run startup cost that Investment 5 works to minimize. +* **Single generic action (recommended)**. One action runs an ordered list of allowed `rad` commands. A single ephemeral control plane is created once per run and reused across all commands, so multi-step flows (deploy, then `app graph`) pay the startup cost once. Adding a new operation usually means allowing a new command in the enum rather than publishing a new action and workflow. The trade-off is a less strongly typed input surface, which is mitigated by validating every command against the allowed-command enum before anything runs. + +**Recommendation**: the single generic `run-rad-commands` action. It minimizes per-run startup cost for multi-step flows, keeps the committed workflow count small, and lets new operations ship as backward-compatible additions to the allowed-command enum rather than as new actions. It also reduces the surface area in the user's repository to a single, highly flexible workflow that can accommodate future functionality: only the published action (which lives outside the user's repository) needs to be updated with new allowed commands, so the user's repository does not need to change. + +### Per-command result artifacts versus a single combined artifact + +We considered uploading a separate result artifact per command instead of one combined `rad-commands-result` artifact. + +* **Per-command artifacts**. Each command's output is uploaded as its own artifact (for example, `rad-command-0`, `rad-command-1`), so the frontend can download a single command's output without retrieving the others. The drawback is that the artifact set varies with the number and order of commands, so the frontend must discover artifact names dynamically, correlating outputs back to the requested order is awkward, and a run produces many small artifacts. +* **Single combined artifact (recommended)**. One `rad-commands-result` artifact contains a `commands` array with an entry per command, in input order, each with the command, exit code, and output. The artifact name is stable and known in advance, the ordering is explicit, and a single download yields the full result of the run. + +**Recommendation**: the single combined `rad-commands-result` artifact. A stable, well-known artifact name with an ordered `commands` array is simpler for the frontend to consume and keeps the versioned contract small. \ No newline at end of file From d3ab28dc2d8acffebe95f4bc460f7cd496546870 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Mon, 29 Jun 2026 20:08:28 -0500 Subject: [PATCH 10/17] Small incremental fixes Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 261 ++++++++++-------- 1 file changed, 148 insertions(+), 113 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 81213a018e3..fc14b86cef5 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -6,9 +6,9 @@ Today, Radius requires users to install Radius on a Kubernetes cluster. That Kubernetes cluster must have identity and access management configured, gateway controllers installed, storage configured, container image registries set up, and other configurations. Only then can Radius be installed. After these are complete, they must all be performed again for the production environment and Radius installed again. -This limits Radius adoption to organizations that have a platform engineering team to perform these tasks. For developers without a platform engineering team, Radius is not a viable option. +This is a heavy requirement to place on any team. Running Radius as a Kubernetes service carries real install and maintenance complexity, and a team must take that on before they can build a single application. Teams that already have platform engineers, or their own Kubernetes expertise, may accept that cost. For everyone else, and especially for developers without Kubernetes expertise, it is too much to ask just to build an app. This makes Radius not a viable option for these users. -Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavy-weight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner, with state stored externally in the same GitHub account. +Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavyweight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner, with state stored externally in the same GitHub account. ### Top level goals @@ -30,12 +30,20 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius ## User profile and challenges -### User persona(s) +### User personas The primary user is a developer working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and likely do not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. A secondary user is a platform engineer evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster and later migrate to a self-hosted Radius installation if needed. +### User problem description + +I am a developer building a cloud-native application. My application is an online storefront made up of several parts that must work together: a containerized web frontend and API, a PostgreSQL database for orders and inventory, a Redis cache for sessions and catalog lookups, and a RabbitMQ message queue for asynchronous order fulfillment. The frontend and API run as containers, while the database, cache, and queue are managed cloud services that must be provisioned, connected to the workloads, and granted the right identity and network access. Developing the application is easy with today's AI coding agents. The hard part is not the code; it is getting all of it deployed and wired together correctly in the cloud. + +What makes this hard is the depth of detail each piece demands. Every managed service has its own configuration to learn: the provisioning options for the database, the cache, and the queue; the managed identities and IAM roles that let my workloads talk to them; the connection strings, endpoints, and secrets that have to be generated and injected into the right containers; and the network rules that allow traffic between them. I have to do all this again for every environment (dev, test, and production), keep them consistent, and keep the dependencies and ordering straight in my head. Radius is appealing precisely because it models an application at this level and handles that wiring for me. But to use Radius today, I first have to provision a Kubernetes cluster and stand up a Radius control plane on it, then do it all over again for a separate production cluster, and keep all of it upgraded, monitored, and secured. That is on top of building my application, and I do not have a platform team to hand it to. + +So in practice I never get to Radius at all. Instead I stitch everything together myself with infrastructure as code: I write Terraform, CloudFormation, or Helm charts against cloud-specific providers, learning each provider's resource types and arguments for every service I use; I wire the connections, managed identities, and network access between the workloads and the services by hand; and I duplicate and tweak it all for each environment while managing state and ordering myself. It is a large amount of low-level, provider-specific code to write and maintain, it is easy to get a detail wrong, and the effort grows with every service and every environment I add. I would rather use a tool that models my application for me, but not if the price of admission is managing a Kubernetes cluster and Radius control plane. + ### Challenge(s) faced by the user * **High barrier to entry**. Before deploying a single application with Radius today, the developer must provision a Kubernetes cluster, configure IAM, install gateway controllers, set up storage, configure a container registry, and install Radius. This takes hours to days and requires Kubernetes expertise. @@ -45,7 +53,7 @@ A secondary user is a platform engineer evaluating Radius. Repo Radius provides ### Positive user outcome -A developer, using a solution built using Repo Radius, can deploy their application to AWS or Azure without having to install Kubernetes or Radius. They can provide their cloud account details and Repo Radius will provision the required infrastructure for their application. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. The state is stored in the same GitHub account. If they outgrow Repo Radius, they can migrate to a self-hosted Radius installation. +A developer, using a solution built using Repo Radius, can deploy their application to AWS or Azure without having to install Kubernetes or Radius. They can create multiple environments and provide their cloud account details, and Repo Radius will provision the required infrastructure for their application. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. The state is stored in the same GitHub account. If they outgrow Repo Radius, they can take their application definition to a self-hosted Radius installation without modification. ## Key scenarios @@ -65,45 +73,34 @@ The developer asks to deploy their application. Cloud resources are provisioned The developer modifies their application and deploys the change to an existing environment. Repo Radius loads the persisted state from the previous deployment and applies changes incrementally. -### Scenario 5: Delete a deployed application - -The developer asks to delete their application. Repo Radius loads the persisted state, deletes the application and its associated cloud resources, and updates the persisted state to reflect the removal. - -### Scenario 6: Retrieve the application graph +### Scenario 5: Retrieve the application graph The developer asks to see the application graph for a deployed application including the resources it comprises, their IDs, and the connections between them. Repo Radius loads the persisted state and returns the JSON of the deployed application graph view without deploying or modifying any resources. -### Scenario 7: Use an updated version of Repo Radius - -Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/run-rad-commands@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag for breaking changes (`@v1`, `@v2` is the standard GitHub Actions semantic versioning convention). Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. - -## Key dependencies and risks - -* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. - -* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. +### Scenario 6: Delete a deployed application -* **Risk: Broad cloud permissions and blast radius**. The IAM role (AWS) or app registration (Azure) assumed via OIDC must hold a significant number of permissions to create and destroy the full range of resources an application may require. A role this broad has a high blast radius: a misbehaving recipe, a compromised workflow, or an over-scoped command could create, modify, or destroy a wide range of cloud resources. Scoping these permissions to the minimum necessary, and isolating them per environment, is an important mitigation to design for. +The developer asks to delete their application. Repo Radius loads the persisted state, deletes the application and its associated cloud resources, and updates the persisted state to reflect the removal. -* **Risk: Recipe and recipe pack backward compatibility**. Because Repo Radius is stateless and provisions on demand, every deployment re-resolves the application's resources against whatever recipe pack version is in effect at that time. Once an application is deployed, it must be assumed that there are live cloud resources backed by a recipe. Recipe and recipe pack versions must therefore be tightly controlled so that a newer recipe version remains backward-compatible with resources provisioned by an earlier version; a breaking recipe change could fail to reconcile, orphan, or inadvertently destroy resources that are already deployed. Recipe and recipe pack versioning needs the same backward-compatibility discipline as the action contract itself. +### Scenario 7: Use an updated version of Repo Radius -## Details of user problem +Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/run-rad-commands@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag (for example, `@v2`) for breaking changes, following the standard GitHub Actions versioning convention. Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. -When I try to deploy my cloud-native application with Radius, I first have to provision a Kubernetes cluster, configure IAM policies, install ingress controllers, set up persistent storage, configure a container registry, and then install Radius. This process takes hours, requires Kubernetes expertise I may not have, and is error-prone. If I want to separate production from non-production workloads, I need a second cluster and a second Radius installation. +## Detailed user experience -Once Radius is running, I am responsible for keeping the Kubernetes cluster and the Radius control plane operational: upgrading Radius, monitoring its health, backing up its state, and securing it. This is a significant ongoing cost for what I really want, which is just to deploy my application to AWS or Azure. +Since Repo Radius is only the backend for a larger end-to-end developer experience, the scenarios described below are intentionally low-level. They describe how Repo Radius behaves for each operation: what it expects going in, what the action does, and what it returns when things succeed or fail. Since Repo Radius does not have a user interface, what the developer sees and clicks is defined by the frontend, which is a separate component covered in its own design. -As a result, I fall back to my existing infrastructure as code solution and do not adopt Radius. +### Scenario 1-2: Set up a new environment and authorize cloud access -## Desired user experience outcome +#### Prerequisites -After Repo Radius is implemented, a frontend can offer developers a complete deploy-to-cloud experience backed by Radius without requiring the developer to provision any infrastructure. The frontend handles user interaction and intent; Repo Radius handles execution. +* A GitHub repository with GitHub Actions enabled. +* Permission to create GitHub Environments in the repository and to configure the cloud provider's identity federation (an Entra ID app registration for Azure, or an IAM OIDC identity provider and role for AWS). -## Detailed user experience +#### Workflow -### Scenario 1-2: Set up a new environment and authorize cloud access +The frontend performs the following steps on the user's behalf. -#### Step 1: Enable Repo Radius for the repository +##### Step 1: Enable Repo Radius for the repository A workflow file in the repository's `.github/workflows/` directory is required for GitHub Actions to run; an action cannot be invoked without one. The frontend therefore writes the two lightweight workflows to the repository on the user's behalf: one to verify cloud authentication and one to run `rad` commands. These workflows are intentionally thin: they contain no Repo Radius logic and simply invoke the published Repo Radius GitHub Actions. All implementation detail lives within the published actions, so a committed workflow only needs to change when the repository adopts a new major version of an action (see Scenario 7). @@ -114,52 +111,15 @@ Repo Radius is published as the following GitHub Actions, each pinned to a match | `radius-verify-cloud-auth.yml` | `radius-project/verify-cloud-auth` | Verify that the GitHub Environment's OIDC configuration can authenticate to the cloud provider before attempting a deployment. | | `radius-run-rad-commands.yml` | `radius-project/run-rad-commands` | Run one or more `rad` CLI commands against the environment. This single action performs every Radius operation (deploying, updating, deleting, and reading) by running the corresponding `rad` commands. | -#### Step 2: Environment setup +##### Step 2: Environment setup The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKS or AKS cluster name and Kubernetes namespace) for application workloads. -The environment stores the following cloud provider and cluster details: - -| Variable | Cloud | Required | Description | -| --- | --- | --- | --- | -| `AWS_ACCOUNT_ID` | AWS | Yes (AWS) | AWS account ID where cloud resources are provisioned. | -| `AWS_EKS_CLUSTER_NAME` | AWS | Yes (AWS) | Name of the target EKS cluster for application workloads. | -| `AWS_REGION` | AWS | Yes (AWS) | AWS region where cloud resources are provisioned. | -| `AZURE_AKS_CLUSTER_NAME` | Azure | Yes (Azure) | Name of the target AKS cluster for application workloads. | -| `AZURE_RESOURCE_GROUP` | Azure | Yes (Azure) | Resource group for provisioned cloud resources. | -| `AZURE_SUBSCRIPTION_ID` | Azure | Yes (Azure) | Subscription ID where cloud resources are provisioned. | -| `KUBERNETES_NAMESPACE` | Both | No | Kubernetes namespace for application workloads. Defaults to `default` if not specified. | - -While the frontend typically creates the GitHub Environment on the user's behalf, the user can also create it directly using the `gh` CLI or the GitHub REST API. For example, using the `gh` CLI to create `dev`, `test`, and `prod` environments and set their variables: - -```bash -for env in dev test prod; do - # Create the environment - gh api --method PUT repos/{owner}/{repo}/environments/$env - - # Set the cloud provider and cluster variables - gh variable set AWS_ACCOUNT_ID --env $env --body "123456789012" - gh variable set AWS_REGION --env $env --body "us-west-2" - gh variable set AWS_EKS_CLUSTER_NAME --env $env --body "my-eks-cluster" - gh variable set KUBERNETES_NAMESPACE --env $env --body "my-app-$env" -done -``` - -If GitHub offers a more declarative approach to creating and configuring environments (for example, a configuration file checked into the repository), that approach can be used instead of the imperative `gh` CLI commands shown above. - -#### Step 3: OIDC setup +##### Step 3: OIDC setup The frontend guides the user through configuring their cloud provider to trust the GitHub Actions OIDC identity provider as a federated identity source, so that the workflow can authenticate using short-lived tokens rather than stored credentials. For Azure, this means creating a federated credential on an Entra ID app registration that trusts tokens issued by GitHub Actions; the resulting client ID, tenant ID, and subscription ID are stored in the GitHub Environment. For AWS, this means creating an IAM OIDC identity provider and an IAM role with a trust policy that accepts GitHub Actions tokens; the role ARN and region are stored in the GitHub Environment. -The environment stores the following authentication details: - -| Variable | Cloud | Required | Description | -| --- | --- | --- | --- | -| `AZURE_CLIENT_ID` | Azure | Yes (Azure) | Client ID of the Entra ID app registration used for OIDC federation. | -| `AZURE_TENANT_ID` | Azure | Yes (Azure) | Entra ID tenant ID. | -| `AWS_ROLE_ARN` | AWS | Yes (AWS) | ARN of the IAM role assumed via OIDC federation. | - -#### Step 4: Verify cloud authentication +##### Step 4: Verify cloud authentication Before the environment is considered ready, the frontend dispatches the `radius-verify-cloud-auth.yml` workflow (which invokes the `radius-project/verify-cloud-auth` action) for the target GitHub Environment. This confirms that the environment's variables and OIDC configuration are complete and correct, so that misconfiguration is surfaced during setup rather than on the first deployment. @@ -167,6 +127,8 @@ The verification performs an end-to-end authentication check: it requests a GitH When an EKS or AKS cluster name is provided in the environment, the verification additionally confirms Kubernetes connectivity: it uses the authenticated cloud credentials to retrieve the cluster's access configuration and confirms that the cluster's API server is reachable. This catches a misnamed cluster, a cluster in a different region or resource group, or an identity that lacks access to the cluster before the first deployment. +#### Workflow outcome and failure cases + The outcome is reported in two complementary ways, consistent with how all Repo Radius workflows return results: * **Workflow run conclusion**. The GitHub Actions run concludes as success or failure, giving the frontend a coarse pass/fail signal it can read directly from the run status via the GitHub Actions API without downloading any files. @@ -174,43 +136,43 @@ The outcome is reported in two complementary ways, consistent with how all Repo The workflow reports one of the following outcomes: -| Outcome | Meaning | Action Exit Code | Example artifact contents | -| --- | --- | --- | --- | -| Success | Authentication succeeded and the credentials are valid. | 0 | `{"outcome": "success", "message": "Authentication succeeded. Environment is ready for deployments."}` | -| Missing configuration | A required variable for the selected cloud provider is absent. | 1 | `{"outcome": "missing_configuration", "message": "Required variable AWS_ROLE_ARN is not set. Re-run environment and OIDC setup to populate the environment."}` | -| Trust not established | The cloud provider rejected the GitHub OIDC token. | 2 | `{"outcome": "trust_not_established", "message": "Cloud provider rejected the GitHub OIDC token. Re-run environment and OIDC setup to correct or verify the IAM role trust policy or app registration federated credential matches the repository, environment, and subject claim."}` | -| Insufficient permissions | Authentication succeeded but the identity lacks permission to perform the check. | 3 | `{"outcome": "insufficient_permissions", "message": "Authenticated successfully, but the AWS IAM role or Azure app registration is missing the required permissions. Re-run the environment and OIDC setup or manually add the missing permission. See the Radius documentation for required permissions."}` | -| Cluster unreachable | A cluster name was provided, but the cluster could not be found or its API server could not be reached. | 4 | `{"outcome": "cluster_unreachable", "message": "Cluster my-eks-cluster could not be found or its API server is unreachable. Correct the EKS cluster name, or grant the identity access to the cluster."}` | -| Provider unreachable | The cloud provider's authentication endpoint could not be reached. | 5 | `{"outcome": "provider_unreachable", "message": "Cloud provider authentication endpoint could not be reached. Re-run the verification workflow to try again. If it persists, check the provider's service status."}` | +| Outcome | Meaning | Example artifact contents | +| --- | --- | --- | +| Authentication succeeded (`success`) | Authentication succeeded and the credentials are valid. | The `verify-cloud-auth-result` artifact with a message such as "Authentication succeeded. Environment is ready for deployments." | +| Missing configuration (`missing_configuration`) | A required variable for the selected cloud provider is absent. | The `verify-cloud-auth-result` artifact with a message such as "A required cloud authentication variable is not set. Re-run environment and OIDC setup to populate the environment." | +| Trust not established (`trust_not_established`) | The cloud provider rejected the GitHub OIDC token. | The `verify-cloud-auth-result` artifact with a message such as "Cloud provider rejected the GitHub OIDC token. Re-run environment and OIDC setup to verify the IAM role trust policy or app registration federated credential matches the repository, environment, and subject claim." | +| Insufficient permissions (`insufficient_permissions`) | Authentication succeeded but the identity lacks permission to perform the check. | The `verify-cloud-auth-result` artifact with a message such as "Authenticated successfully, but the AWS IAM role or Azure app registration is missing the required permissions. Re-run the environment and OIDC setup or manually add the missing permission. See the Radius documentation for required permissions." | +| Cluster unreachable (`cluster_unreachable`) | A cluster name was provided, but the cluster could not be found or its API server could not be reached. | The `verify-cloud-auth-result` artifact with a message such as "Cluster my-eks-cluster could not be found or its API server is unreachable. Correct the EKS cluster name, or grant the identity access to the cluster." | +| Provider unreachable (`provider_unreachable`) | The cloud provider's authentication endpoint could not be reached. | The `verify-cloud-auth-result` artifact with a message such as "Cloud provider authentication endpoint could not be reached. Re-run the verification workflow to try again. If it persists, check the provider's service status." | -The result artifact is uploaded under a well-known, stable artifact name (for example, `verify-result`) and is available to the frontend via the GitHub Actions API. The artifact name and the JSON schema (the set of fields and the allowed `outcome` values) form part of the stable contract between Repo Radius and any frontend, so they must remain backward-compatible within a major version. +The result artifact is uploaded under a well-known, stable artifact name (for example, `verify-cloud-auth-result`) and is available to the frontend via the GitHub Actions API. The artifact name and the JSON schema form part of the stable contract between Repo Radius and any frontend, so they must remain backward-compatible within a major version. ### Scenario 3-4: Deploy a new or updated application +From the developer's point of view, this is the step that matters most: they ask to deploy the storefront to an environment and need to come away knowing it actually worked, or, if it did not, exactly what failed. The frontend turns that intent into a workflow dispatch and reports the result back. + +#### Prerequisites + +* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). +* Cloud authentication has been verified for the environment and `verify-cloud-auth-result` reported `success` (Scenario 2). +* The application definition is present in the repository. +* The `radius-run-rad-commands.yml` workflow is committed to the repository (Step 1). + +#### Workflow + The frontend deploys (or updates) an application by dispatching the `radius-run-rad-commands.yml` workflow (which invokes the `radius-project/run-rad-commands` action) for the target GitHub Environment. Deployment is expressed as a `rad deploy` command in the `rad_commands` input; an update uses the same command against the same environment, and Radius reconciles the change incrementally from the persisted state. The workflow accepts the following inputs: | Input | Value | | --- | --- | | `environment` | The GitHub Environment name | -| `rad_commands` | `['deploy .radius/app.bicep', 'app graph my-app -o json']` | +| `rad_commands` | `['deploy .radius/app.bicep', 'app graph storefront -o json']` | > [!IMPORTANT] > Repo Radius does not accept arbitrary `rad` invocations. Each entry in `rad_commands` is validated against an enum of allowed commands so that commands which are not applicable to the ephemeral, per-run model (for example, commands that manage or upgrade a persistent control plane, switch workspaces, or change installation state) cannot run, and so the contract stays narrow and reviewable. Validation happens before the control plane is created or any command runs, so a disallowed command errors out and fails fast without provisioning anything. The specific set of allowed commands is defined in the technical design. The enum is part of the stable contract and is versioned with the action; additional commands may be introduced in backward-compatible releases. -Example dispatch. The frontend deploys the application with `rad deploy`, then captures the resulting application graph with `rad app graph`: - -```json -POST /repos/{owner}/{repo}/actions/workflows/radius-run-rad-commands.yml/dispatches -{ - "ref": "main", - "inputs": { - "environment": "production", - "rad_commands": "[\"deploy .radius/app.bicep\", \"app graph my-app -o json\"]" - } -} -``` +To deploy the storefront and capture its application graph, the frontend dispatches the workflow for the target environment (`production`) with the two `rad` commands shown above in the `rad_commands` input. -The `radius-run-rad-commands.yml` workflow contains only one action calling the `radius-project/run-rad-commands@v1` GitHub Action which is published to the GitHub Marketplace. The `radius-project/run-rad-commands` action performs the following: +The `radius-run-rad-commands.yml` workflow contains only one action calling the `radius-project/run-rad-commands@v1` GitHub Action which is published to the GitHub Marketplace. This is deliberate: the workflow file lives in the user's repository, where logic is difficult to update because each change requires a commit to every consuming repository. By keeping the workflow as a thin wrapper that simply invokes the published action, all of the real logic lives in the action itself and can be updated centrally on the GitHub Marketplace. Users automatically pick up new backward-compatible releases without any change to the workflow in their repository. The `radius-project/run-rad-commands` action performs the following: 1. **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. @@ -226,35 +188,88 @@ The `radius-run-rad-commands.yml` workflow contains only one action calling the 1. **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. -The workflow reports one of the following outcomes: +#### Workflow outcome and failure cases -| Outcome | Meaning | Action Exit Code | Example artifact contents | -| --- | --- | --- | --- | -| All commands succeeded | Every command ran successfully. | 0 | The `rad-commands-result` artifact whose `commands` array has an entry per command, each with the command and its output (such as the `rad deploy` output and the JSON application graph). | -| A command failed | A command did not complete successfully; remaining commands are not run. | 1 | The `rad-commands-result` artifact whose last entry is the failed command including its exit code and error output. Entries for commands that did not run are absent. | -| Disallowed command | One or more commands are not in the allowed-command enum; the request is rejected and no command runs. | 2 | The `rad-commands-result` artifact identifying the disallowed command. | +If a `rad` command fails, the action stops before running the remaining commands, so `command_failed` reports a partial result whose final entry is the failed command. `state_persist_failed` is the other partial-state case: the commands ran, but the updated data store could not be written back, so the cloud may contain resources the persisted state does not reflect. The workflow reports one of the following outcomes: -### Scenario 5: Delete a deployed application +| Outcome | Meaning | Example artifact contents | +| --- | --- | --- | +| All commands succeeded (`succeeded`) | Every command ran successfully. | The `run-rad-commands-result` artifact whose `commands` array has an entry per command, each with the command and its output (such as the `rad deploy` output and the JSON application graph). | +| A command failed (`command_failed`) | A command did not complete successfully; remaining commands are not run. | The `run-rad-commands-result` artifact whose last entry is the failed command including its exit code and error output. Entries for commands that did not run are absent. | +| Disallowed command (`disallowed_command`) | One or more commands are not in the allowed-command enum; the request is rejected and no command runs. | The `run-rad-commands-result` artifact identifying the disallowed command. | +| Environment not found (`environment_not_found`) | The named GitHub Environment does not exist or has no configuration. The request is rejected before authentication and no command runs. | The `run-rad-commands-result` artifact with an empty `commands` array and a message such as "GitHub Environment 'production' was not found or has no configuration. Create the environment and complete cloud authentication setup before deploying." | +| Authentication failed (`authentication_failed`) | Cloud authentication failed before any command ran; the GitHub OIDC token could not be exchanged for valid AWS or Azure credentials. | The `run-rad-commands-result` artifact with an empty `commands` array and a message such as "Failed to authenticate to AWS. The IAM role trust policy or permissions may have changed since the environment was verified. Re-run cloud authentication verification." | +| State load failed (`state_load_failed`) | The persisted Radius data store could not be read or restored into the control plane, so no command ran. | The `run-rad-commands-result` artifact with an empty `commands` array and a message indicating the stored state is missing or unreadable. | +| State persist failed (`state_persist_failed`) | Commands ran, but the updated data store could not be written back. The cloud may now contain resources that the persisted state does not reflect. | The `run-rad-commands-result` artifact including the command results plus a message warning that state was not saved, so a subsequent run may not reflect the resources that were just provisioned. | -The frontend deletes an application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app delete` command in the `rad_commands` input. The workflow accepts the same inputs as the deploy scenario: +#### The `run-rad-commands-result` artifact + +The `run-rad-commands-result` artifact is the contract between Repo Radius and any frontend, so its name and schema are fixed and versioned with the action. It carries the overall `outcome`, a human-readable `message`, and an ordered `commands` array with one entry per command (the command, its outcome, and its captured output). For example, deploying the storefront and then reading its graph yields an artifact whose `commands` array has two succeeded entries: the `rad deploy` output listing the provisioned resources, and the JSON application graph. + +```json +{ + "outcome": "succeeded", + "message": "All 2 commands succeeded.", + "commands": [ + { + "command": "deploy .radius/app.bicep", "outcome": "succeeded", + "output": "Deployment Complete ..." + }, + { + "command": "app graph storefront -o json", + "outcome": "succeeded", "output": "{ ... }" + } + ] +} +``` + +The complete schema is defined in the technical design. + +### Scenario 5: Retrieve the application graph + +#### Prerequisites + +* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). +* Cloud authentication has been verified for the environment (Scenario 2). +* The application has been deployed to the target environment (Scenario 3-4). + +#### Workflow + +The frontend retrieves the application graph for a deployed application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app graph` command in the `rad_commands` input. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources, so the data store is unchanged. | Input | Value | | --- | --- | | `environment` | The GitHub Environment name | -| `rad_commands` | `['app delete my-app --yes']` | +| `rad_commands` | `['app graph storefront -o json']` | -The action follows the same stages as the deploy scenario, running `rad app delete "" --yes` to delete the application and its associated cloud resources. The updated data store is then persisted so the removal is reflected in subsequent runs. The command's output is included in the single `rad-commands-result` artifact, and the workflow reports the same outcomes described in the deploy scenario. +The action runs `rad app graph storefront -o json` and writes the result to the `run-rad-commands-result` artifact; on success, the artifact's single entry contains the JSON of the deployed application graph view. For the storefront, the graph shows the `frontend` and `api` containers, with the `api` connected to the `orders-db` database, the `cache`, and the `orders-queue`. -### Scenario 6: Retrieve the application graph +#### Workflow outcome and failure cases -The frontend retrieves the application graph for a deployed application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app graph` command in the `rad_commands` input. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources, so the data store is unchanged. +The workflow reports the same outcomes as the deploy scenario. + +### Scenario 6: Delete a deployed application + +#### Prerequisites + +* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). +* Cloud authentication has been verified for the environment (Scenario 2). +* The application has been deployed to the target environment (Scenario 3-4). + +#### Workflow + +The frontend deletes an application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app delete` command in the `rad_commands` input. The workflow accepts the same inputs as the deploy scenario: | Input | Value | | --- | --- | | `environment` | The GitHub Environment name | -| `rad_commands` | `['app graph my-app -o json']` | +| `rad_commands` | `['app delete storefront --yes']` | + +The action follows the same stages as the deploy scenario, running `rad app delete storefront --yes` to delete the application and its associated cloud resources (the `orders-db` PostgreSQL database, the `cache` Redis instance, and the `orders-queue` RabbitMQ queue). The command's output is included in the single `run-rad-commands-result` artifact. -The action runs `rad app graph "" -o json` and writes the result to the single `rad-commands-result` artifact. The workflow reports the same outcomes described in the deploy scenario; on success, the artifact's single entry contains the JSON of the deployed application graph view. +#### Workflow outcome and failure cases + +The workflow reports the same outcomes as the deploy scenario. ### Scenario 7: Use an updated version of Repo Radius @@ -268,7 +283,7 @@ When a change would break the established contract (for example, the workflow in ### Investment 1: Deployment to an external Kubernetes cluster -Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying workloads to the developer's target cluster, so the control plane and workload clusters must be fully decoupled. This capability is delivered by the [Deploy to External AKS and EKS Clusters](../environments/2026-05-external-kubernetes.md) feature, which names the external cluster on the environment's cloud provider block (`aws.eksClusterName` or `azure.aksClusterName`) and acquires Kubernetes API access on demand using the AWS or Azure credentials already registered with Radius. Repo Radius depends on that feature and reuses its environment shape and on-demand credential acquisition rather than introducing a separate mechanism. +Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying workloads to the developer's target cluster, so the control plane and workload clusters must be fully decoupled. This capability is delivered by the [Deploy to External AKS and EKS Clusters](../environments/2026-05-external-kubernetes.md) feature, which names the external cluster on the environment's cloud provider block. The method of acquiring Kubernetes API access is to be defined in the technical design. ### Investment 2: Externalized, portable Radius data store @@ -276,7 +291,7 @@ Move the Radius data store out of the in-cluster database so that state persists ### Investment 3: Repo Radius GitHub Actions with a stable contract -Build and publish the two Repo Radius actions (`verify-cloud-auth` and `run-rad-commands`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema. This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward-compatible within a major version. +Build and publish the two Repo Radius actions (`verify-cloud-auth` and `run-rad-commands`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema (see the `run-rad-commands-result` artifact in Scenario 3-4). This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward compatible within a major version. ### Investment 4: Lightweight workflows for each action @@ -284,12 +299,22 @@ Provide the thin, ready-to-commit workflow file that pairs with each action (`ra ### Investment 5: Fast ephemeral control plane startup -Provision the k3d cluster, install the `rad` CLI, and install Radius quickly enough that the per-run startup cost is acceptable within a GitHub Actions runner. Because the control plane is created and torn down on every operation, startup time is on the critical path for every user-facing action and is the primary determinant of perceived responsiveness. +Provision the k3d cluster, install the `rad` CLI, and install Radius quickly enough that the per-run startup cost is acceptable within a GitHub Actions runner. Because the control plane is created and torn down on every operation, startup time is on the critical path for every user-facing action and is the primary determinant of perceived responsiveness. The approach to optimizing control plane startup time is to be defined in the technical design. ### Investment 6: Cloud credential integration via OIDC Integrate with GitHub's OIDC federation to provide short-lived AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the per-run authentication steps, injection of session credentials into the Radius pods for Terraform provider access, and the `verify-cloud-auth` preflight check that validates the environment's configuration before the first deployment. +## Key dependencies and risks + +* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. + +* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. + +* **Risk: Broad cloud permissions and blast radius**. The IAM role (AWS) or app registration (Azure) assumed via OIDC must hold a significant number of permissions to create and destroy the full range of resources an application may require. A role this broad has a high blast radius: a misbehaving recipe, a compromised workflow, or an over-scoped command could create, modify, or destroy a wide range of cloud resources. Scoping these permissions to the minimum necessary, and isolating them per environment, is an important mitigation to design for. + +* **Risk: Recipe and recipe pack backward compatibility**. Because Repo Radius is stateless and provisions on demand, every deployment re-resolves the application's resources against whatever recipe pack version is in effect at that time. Once an application is deployed, it must be assumed that there are live cloud resources backed by a recipe. Recipe and recipe pack versions must therefore be tightly controlled so that a newer recipe version remains backward-compatible with resources provisioned by an earlier version; a breaking recipe change could fail to reconcile, orphan, or inadvertently destroy resources that are already deployed. Recipe and recipe pack versioning needs the same backward-compatibility discipline as the action contract itself. + ## Alternatives considered ### Multiple operation-specific actions versus a single generic action @@ -299,13 +324,23 @@ We considered publishing a separate action for each operation (for example, `rad * **Multiple operation-specific actions**. Each action exposes a narrow, strongly typed input surface (for example, `radius-deploy` takes an application path; `radius-destroy` takes an application name), so the contract is self-documenting and each action validates only the inputs relevant to it. The drawback is that every new operation requires a new action, a new workflow file committed to the repository, and a new major-version lifecycle to maintain. Composing several operations in one run (deploy, then read the graph) requires multiple workflow dispatches and multiple ephemeral control plane startups, multiplying the per-run startup cost that Investment 5 works to minimize. * **Single generic action (recommended)**. One action runs an ordered list of allowed `rad` commands. A single ephemeral control plane is created once per run and reused across all commands, so multi-step flows (deploy, then `app graph`) pay the startup cost once. Adding a new operation usually means allowing a new command in the enum rather than publishing a new action and workflow. The trade-off is a less strongly typed input surface, which is mitigated by validating every command against the allowed-command enum before anything runs. -**Recommendation**: the single generic `run-rad-commands` action. It minimizes per-run startup cost for multi-step flows, keeps the committed workflow count small, and lets new operations ship as backward-compatible additions to the allowed-command enum rather than as new actions. It also reduces the surface area in the user's repository to a single, highly flexible workflow that can accommodate future functionality: only the published action (which lives outside the user's repository) needs to be updated with new allowed commands, so the user's repository does not need to change. +**Recommendation**: The single generic `run-rad-commands` action. It minimizes per-run startup cost for multi-step flows, keeps the committed workflow count small, and lets new operations ship as backward compatible additions to the allowed-command enum rather than as new actions. It also reduces the surface area in the user's repository to a single, highly flexible workflow that can accommodate future functionality: only the published action (which lives outside the user's repository) needs to be updated with new allowed commands, so the user's repository does not need to change. ### Per-command result artifacts versus a single combined artifact -We considered uploading a separate result artifact per command instead of one combined `rad-commands-result` artifact. +We considered uploading a separate result artifact per command instead of one combined `run-rad-commands-result` artifact. * **Per-command artifacts**. Each command's output is uploaded as its own artifact (for example, `rad-command-0`, `rad-command-1`), so the frontend can download a single command's output without retrieving the others. The drawback is that the artifact set varies with the number and order of commands, so the frontend must discover artifact names dynamically, correlating outputs back to the requested order is awkward, and a run produces many small artifacts. -* **Single combined artifact (recommended)**. One `rad-commands-result` artifact contains a `commands` array with an entry per command, in input order, each with the command, exit code, and output. The artifact name is stable and known in advance, the ordering is explicit, and a single download yields the full result of the run. +* **Single combined artifact (recommended)**. One `run-rad-commands-result` artifact contains a `commands` array with an entry per command, in input order, each with the command, exit code, and output. The artifact name is stable and known in advance, the ordering is explicit, and a single download yields the full result of the run. + +**Recommendation**: The single combined `run-rad-commands-result` artifact. A stable, well-known artifact name with an ordered `commands` array is simpler for the frontend to consume and keeps the versioned contract small. + +### Environments defined in a repository file versus GitHub Environments + +We considered letting the user define multiple environments in a checked-in file (for example, an `env.bicep` in the repository) instead of modeling each environment as a GitHub Environment. + +* **Environments defined in a repository file**. A single `env.bicep` declares every environment (dev, test, production) and its cloud provider and cluster details in one place under version control, so the full set of environments is reviewable in a pull request and lives alongside the application. The drawback is that it duplicates a concept GitHub already provides. Cloud credentials must still be stored as GitHub Environment secrets, so an env.bicep would split related configuration across two locations: non-secret details in the file and secrets in the environment. + +* **GitHub Environments (recommended)**. Each environment is a GitHub Environment whose variables hold the cloud provider and cluster details. This reuses GitHub's native environment model including scoped secrets. The downside is that GitHub Environments are not easily created in bulk or declaratively; each environment and its variables are configured imperatively through the GitHub UI, the `gh` CLI, or the REST API rather than from a single checked-in file. The offsetting upside is that users likely already know how to create GitHub Environments and may already have tooling or automation for managing them. -**Recommendation**: the single combined `rad-commands-result` artifact. A stable, well-known artifact name with an ordered `commands` array is simpler for the frontend to consume and keeps the versioned contract small. \ No newline at end of file +**Recommendation**: Model environments as GitHub Environments only. It reuses GitHub's native scoping, protection, and OIDC integration rather than reinventing them in a repository file, and it keeps a single source of truth for environment configuration that the workflows and the frontend already consume. From 60315fe77a2747d592beab10155f2b5bfe70a53b Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Wed, 1 Jul 2026 02:10:21 -0500 Subject: [PATCH 11/17] Major v3 rewrite Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 455 +++++++++++------- 1 file changed, 282 insertions(+), 173 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index fc14b86cef5..4041bb0eaa0 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -25,303 +25,404 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Supporting multi-repo applications is deferred to a later date. -* Customizing the recipe pack is deferred to a later date. The initial release uses the default AWS and Azure recipe pack that ships with Radius. -* Creating mature, production-grade recipe packs for AWS and Azure. Repo Radius depends on the default recipe pack that ships with Radius; hardening and expanding that recipe pack is separate work outside the scope of Repo Radius. +* Customizing, hardening, or expanding the recipe pack. The initial release uses the default AWS and Azure recipe pack that ships with Radius as-is; improving that pack is separate work, deferred to a later date. -## User profile and challenges +## Definition of terms -### User personas +Throughout this document, several terms are used with a specific meaning in the context of Repo Radius. -The primary user is a developer working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and likely do not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. +**Repo Radius** – The on-demand delivery model described in this specification, in which Radius runs ephemerally inside a GitHub Actions runner with state stored in the same GitHub account. + +**Self-hosted Radius** – The existing deployment model in which Radius runs as a persistent control plane installed on a Kubernetes cluster. + +**Frontend** – The separate user-facing component (such as the Copilot app) that drives Repo Radius by dispatching GitHub Action workflows and presenting their results. + +**Ephemeral control plane** – A Radius control plane created on demand inside a GitHub Actions runner for the duration of a single operation and torn down when it completes. + +**Workload cluster** – The developer's own EKS or AKS cluster where application workloads are deployed, separate from the ephemeral control plane. + +**Radius data store** – The persisted Radius state (resource data, application graphs, deployment history, and Terraform state). In self-hosted Radius this is held in etcd. + +**GitHub Environment** – A native GitHub feature used to store a deployment environment's cloud provider details, workload cluster details, and scoped secrets. -A secondary user is a platform engineer evaluating Radius. Repo Radius provides a low-friction entry point: they can try Radius on a real application without provisioning a Kubernetes cluster and later migrate to a self-hosted Radius installation if needed. +**GitHub Deployment** – A native GitHub record that associates a deployment with a GitHub Environment and a specific git commit, used to track which version of the application is currently deployed to each environment. -### User problem description +**OIDC identity federation** – A mechanism for exchanging a short-lived GitHub Actions token for cloud provider credentials without storing long-lived secrets. -I am a developer building a cloud-native application. My application is an online storefront made up of several parts that must work together: a containerized web frontend and API, a PostgreSQL database for orders and inventory, a Redis cache for sessions and catalog lookups, and a RabbitMQ message queue for asynchronous order fulfillment. The frontend and API run as containers, while the database, cache, and queue are managed cloud services that must be provisioned, connected to the workloads, and granted the right identity and network access. Developing the application is easy with today's AI coding agents. The hard part is not the code; it is getting all of it deployed and wired together correctly in the cloud. +## User profile and challenges + +### User personas + +The primary user is a developer working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and likely do not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. -What makes this hard is the depth of detail each piece demands. Every managed service has its own configuration to learn: the provisioning options for the database, the cache, and the queue; the managed identities and IAM roles that let my workloads talk to them; the connection strings, endpoints, and secrets that have to be generated and injected into the right containers; and the network rules that allow traffic between them. I have to do all this again for every environment (dev, test, and production), keep them consistent, and keep the dependencies and ordering straight in my head. Radius is appealing precisely because it models an application at this level and handles that wiring for me. But to use Radius today, I first have to provision a Kubernetes cluster and stand up a Radius control plane on it, then do it all over again for a separate production cluster, and keep all of it upgraded, monitored, and secured. That is on top of building my application, and I do not have a platform team to hand it to. +A secondary user is a platform engineer who tailors Repo Radius for their teams, customizing recipes and predefining governed environments (User Journey 5) so that developers deploy within approved organizational standards. This tailoring role is largely deferred beyond the initial release. -So in practice I never get to Radius at all. Instead I stitch everything together myself with infrastructure as code: I write Terraform, CloudFormation, or Helm charts against cloud-specific providers, learning each provider's resource types and arguments for every service I use; I wire the connections, managed identities, and network access between the workloads and the services by hand; and I duplicate and tweak it all for each environment while managing state and ordering myself. It is a large amount of low-level, provider-specific code to write and maintain, it is easy to get a detail wrong, and the effort grows with every service and every environment I add. I would rather use a tool that models my application for me, but not if the price of admission is managing a Kubernetes cluster and Radius control plane. +### The developer's problem -### Challenge(s) faced by the user +I am a developer building a cloud-native application, an online storefront: a web frontend and API as containers, backed by a PostgreSQL database, a Redis cache, and a Kafka queue that each must be provisioned, connected to the workloads, and granted the right identity and network access. With today's AI coding agents the code is easy; the hard part is getting all of it deployed and wired together correctly in the cloud. Radius models an application at exactly this level and handles that wiring, but using it today first means provisioning a Kubernetes cluster and standing up a control plane, then repeating that for every environment and keeping it upgraded, monitored, and secured. Without a platform team, that price of admission is too high, so I fall back to traditional IaC tools like Terraform, CloudFormation, or Helm. * **High barrier to entry**. Before deploying a single application with Radius today, the developer must provision a Kubernetes cluster, configure IAM, install gateway controllers, set up storage, configure a container registry, and install Radius. This takes hours to days and requires Kubernetes expertise. +* **Low-level, provider-specific wiring**. Without Radius, the developer owns a large body of provider-specific Terraform, CloudFormation, or Helm, even when an LLM generates the first draft. They still have to understand each provider's resource types, wire the managed identities, connection strings, endpoints, secrets, and network rules between workloads and services, and review and maintain it all. It is easy to get a detail wrong, and the effort grows with every service added. +* **Environment duplication**. The developer repeats this work for every environment (dev, test, and production), keeping them consistent and the dependencies and ordering straight by hand. With self-hosted Radius, separating production from non-production also requires separate Kubernetes clusters, each with its own Radius control plane. * **Ongoing operational burden**. A persistent Radius control plane must be upgraded, monitored, and secured. For a developer who just wants to deploy an application, this overhead is disproportionate to the value. -* **Environment duplication**. Separating production from non-production workloads requires separate Kubernetes clusters, each with its own Radius control plane. This doubles the setup and operational cost. -* **No try-before-you-buy path**. There is no lightweight way to experience Radius's value proposition without committing to the full Kubernetes setup. -### Positive user outcome +### The platform engineer's problem + +I am a platform engineer supporting several teams. I want my developers to deploy on their own, but I am accountable for what lands in our cloud accounts. The default recipes are a good start but do not match our standards for naming, tagging, network and security baselines, approved services and SKUs, and cost controls; and today each repository configures its own cloud access and environments, which I cannot easily govern. I want to set recipes and approved environments once and have every team inherit them, so developers keep their fast, self-service experience while I keep the consistency, security, and governance I am accountable for. -A developer, using a solution built using Repo Radius, can deploy their application to AWS or Azure without having to install Kubernetes or Radius. They can create multiple environments and provide their cloud account details, and Repo Radius will provision the required infrastructure for their application. Radius runs on demand within GitHub Actions, starts quickly, deploys their application, and shuts down. The state is stored in the same GitHub account. If they outgrow Repo Radius, they can take their application definition to a self-hosted Radius installation without modification. +* **No way to enforce organizational standards**. The default recipes do not match the organization's naming, tagging, network and security baselines, approved services and SKUs, or cost controls, so ungoverned deployments produce non-compliant resources that must be found and remediated after the fact. +* **Ungoverned, per-repository environments**. Each repository configures its own identity federation and targets whatever cloud account, region, and Kubernetes cluster a developer chooses, leaving the platform engineer without central visibility or assurance that production is separated from non-production. +* **No central place to tailor Repo Radius**. There is no single place to customize recipes and predefine approved environments once and have every team inherit them. -## Key scenarios +### Positive user outcome -### Scenario 1: Set up a new environment +A developer can deploy to AWS or Azure without installing Kubernetes or Radius: they create environments with their cloud account details, and Repo Radius provisions the infrastructure on demand within GitHub Actions, storing state in the same GitHub account. If they outgrow it, the same application definition moves to a self-hosted Radius installation unchanged. -The developer creates an environment by providing cloud account details (e.g., AWS account ID and region, or Azure subscription and resource group). The environment is stored as a GitHub Environment and reused across subsequent deployments. +A platform engineer sets organizational recipes and governed environments once and every team inherits them, so developers keep a fast, self-service experience while the platform engineer keeps the consistency, security, and governance they are accountable for. -### Scenario 2: Authorize cloud access +## User journeys -The developer connects their AWS or Azure account to GitHub using OIDC identity federation so that deployments can provision cloud resources using short-lived tokens rather than stored credentials. +The experience is organized into five journeys. The first four follow the developer's lifecycle of an application: getting it running, iterating on it, operating it across environments, and evolving it over time. The fifth covers the platform engineer who tailors Repo Radius for a team. Each journey is made up of user stories that describe a single thing the user wants to accomplish. The journeys and their stories are detailed in the sections that follow. -### Scenario 3: Deploy an application from a GitHub repository +Repo Radius is the backend for these journeys, not a user interface. A separate frontend (such as the Copilot app) turns the user's intent into GitHub Action workflow runs and shows the results. The user stories describe what the user wants to accomplish and what Repo Radius does on each operation; what the user sees and clicks is defined by the frontend. -The developer asks to deploy their application. Cloud resources are provisioned on AWS or Azure without any prior Kubernetes cluster setup, Radius installation, or infrastructure configuration. +## User Journey 1: Get started -### Scenario 4: Update a deployed application +User Journey 1 covers a developer taking a cloud-native application from a GitHub repository to a running, reachable deployment in their own cloud account for the first time. It spans connecting the repository to a cloud environment and then deploying the application and viewing what was created and how to reach it. The online storefront introduced above is the running example throughout these journeys. -The developer modifies their application and deploys the change to an existing environment. Repo Radius loads the persisted state from the previous deployment and applies changes incrementally. +This journey assumes a GitHub repository with GitHub Actions enabled, a well-formed application definition (`app.bicep`) already created and committed to the repository, and that the developer can create GitHub Environments and configure their cloud provider's identity federation (an Entra ID app registration for Azure, or an IAM OIDC identity provider and role for AWS). -### Scenario 5: Retrieve the application graph +### User Story 1.1: Connect my repository to a cloud environment -The developer asks to see the application graph for a deployed application including the resources it comprises, their IDs, and the connections between them. Repo Radius loads the persisted state and returns the JSON of the deployed application graph view without deploying or modifying any resources. +> As a developer, I want to connect my repository to a cloud account once, so that every deployment can provision resources securely without me managing credentials. -### Scenario 6: Delete a deployed application +#### Summary -The developer asks to delete their application. Repo Radius loads the persisted state, deletes the application and its associated cloud resources, and updates the persisted state to reflect the removal. +The developer chooses where the application should run (an AWS account and region, or an Azure subscription and resource group) and names the environment (for example, `dev`). Rather than storing long-lived cloud credentials, they set up OIDC identity federation so that every operation authenticates with short-lived tokens. The connection is verified end to end before any deployment, so misconfiguration surfaces during setup rather than on the first deployment. -### Scenario 7: Use an updated version of Repo Radius +#### User Experience -Repo Radius is published as a set of versioned GitHub Actions (e.g., `radius-project/run-rad-commands@v1`). The developer (or the frontend on their behalf) references a major version tag such as `@v1` to receive backward-compatible updates automatically, and moves to the next major tag (for example, `@v2`) for breaking changes, following the standard GitHub Actions versioning convention. Because there is no persistent control plane, the developer never performs a manual upgrade; the next deployment simply runs the referenced version. +The developer works entirely through the frontend, which walks them through three steps: -## Detailed user experience +1. **Enable Repo Radius for the repository.** The frontend asks the developer for permission to enable Radius for the repository. On the developer's acknowledgement, it commits the required GitHub Action workflows to the repository so that GitHub Actions has a dispatchable entry point for each Radius operation. -Since Repo Radius is only the backend for a larger end-to-end developer experience, the scenarios described below are intentionally low-level. They describe how Repo Radius behaves for each operation: what it expects going in, what the action does, and what it returns when things succeed or fail. Since Repo Radius does not have a user interface, what the developer sees and clicks is defined by the frontend, which is a separate component covered in its own design. +2. **Create an environment.** The frontend guides the developer through creating an environment, prompting for the environment name (for example, `dev`), whether it targets AWS or Azure, the cloud account details (AWS account and region, or Azure subscription and resource group), and the workload cluster details (the EKS or AKS cluster name and Kubernetes namespace). -### Scenario 1-2: Set up a new environment and authorize cloud access +3. **Set up cloud access.** The frontend guides the developer through establishing the OIDC trust connection between GitHub and their cloud provider. The developer follows the prompts to create the trust on their cloud account, and the frontend captures the resulting identifiers. -#### Prerequisites +Once these steps are complete, the frontend verifies the connection on the developer's behalf and reports whether the environment is ready. -* A GitHub repository with GitHub Actions enabled. -* Permission to create GitHub Environments in the repository and to configure the cloud provider's identity federation (an Entra ID app registration for Azure, or an IAM OIDC identity provider and role for AWS). +#### Result -#### Workflow +Completing these steps produces the following, all stored in the developer's own GitHub account: -The frontend performs the following steps on the user's behalf. +* **Two committed workflows** in `.github/workflows/`. Each is a thin wrapper that invokes a published Repo Radius GitHub Action pinned to a major version tag (for example, `@v1`), so a committed workflow only changes when the repository adopts a new major version. -##### Step 1: Enable Repo Radius for the repository + | Workflow | Action | Purpose | + | --- | --- | --- | + | `radius-verify-cloud-auth.yml` | `radius-project/verify-cloud-auth` | Verify the environment's OIDC configuration can authenticate to the cloud provider before a deployment is attempted. | + | `radius-run-rad-commands.yml` | `radius-project/run-rad-commands` | Run one or more allowed `rad` commands against the environment. This single action performs every Radius operation (deploying, updating, deleting, and reading). | -A workflow file in the repository's `.github/workflows/` directory is required for GitHub Actions to run; an action cannot be invoked without one. The frontend therefore writes the two lightweight workflows to the repository on the user's behalf: one to verify cloud authentication and one to run `rad` commands. These workflows are intentionally thin: they contain no Repo Radius logic and simply invoke the published Repo Radius GitHub Actions. All implementation detail lives within the published actions, so a committed workflow only needs to change when the repository adopts a new major version of an action (see Scenario 7). +* **A GitHub Environment** holding the environment's configuration as variables: the cloud provider details (AWS account and region, or Azure subscription and resource group), the workload cluster details (the EKS or AKS cluster name and Kubernetes namespace), and the OIDC identifiers (for Azure, the client ID, tenant ID, and subscription ID; for AWS, the IAM role ARN and region). Modeling each environment as a GitHub Environment reuses GitHub's native scoping, protection rules, and secret storage, and provides a single source of truth that both the workflows and the frontend consume. -Repo Radius is published as the following GitHub Actions, each pinned to a matching major version (e.g., `@v1`): +* **An OIDC trust configuration** on the cloud provider: for Azure, a federated credential on an Entra ID app registration; for AWS, an IAM OIDC identity provider and an IAM role whose trust policy accepts GitHub Actions tokens. The trust is scoped to the repository (matching a subject such as `repo:my-org/my-repo:*`) rather than to an individual environment, so every environment in the repository that targets this cloud account shares it. -| Workflow | Action | Purpose | -| --- | --- | --- | -| `radius-verify-cloud-auth.yml` | `radius-project/verify-cloud-auth` | Verify that the GitHub Environment's OIDC configuration can authenticate to the cloud provider before attempting a deployment. | -| `radius-run-rad-commands.yml` | `radius-project/run-rad-commands` | Run one or more `rad` CLI commands against the environment. This single action performs every Radius operation (deploying, updating, deleting, and reading) by running the corresponding `rad` commands. | +To confirm the environment is ready, the frontend dispatches `radius-verify-cloud-auth.yml`, which runs the `radius-project/verify-cloud-auth` action. The action requests a GitHub Actions OIDC token, exchanges it with the cloud provider, confirms the resulting short-lived credentials are valid, and verifies the identity holds the required permissions. When a Kubernetes cluster name is present, it also confirms the cluster's API server is reachable. It does not provision, modify, or delete anything. -##### Step 2: Environment setup +The frontend alerts the developer by examining the verify workflow: it reads the run's success or failure conclusion for a coarse signal, and downloads the `verify-cloud-auth-result` artifact, a small JSON document with the specific outcome, a human-readable message, and remediation guidance. The artifact name and schema are a stable contract that the frontend can rely on. On `success`, the frontend reports that the environment is ready, and it is reused by every later deployment. -The frontend creates a GitHub Environment. The environment stores cloud provider details (e.g., AWS account and region, or Azure subscription and resource group) and Kubernetes cluster details (e.g., the EKS or AKS cluster name and Kubernetes namespace) for application workloads. +Because this trust is scoped to the repository and stores no secret, adding or renaming an environment that targets the same cloud account needs no cloud-side change, and there are no credentials to rotate. It changes in only three cases: the repository is renamed or moved, a new cloud account or subscription is targeted, or the identity's permissions must change. Authentication problems from later drift are handled in the troubleshooting flow (User Story 2.4). -##### Step 3: OIDC setup +#### Exceptions -The frontend guides the user through configuring their cloud provider to trust the GitHub Actions OIDC identity provider as a federated identity source, so that the workflow can authenticate using short-lived tokens rather than stored credentials. For Azure, this means creating a federated credential on an Entra ID app registration that trusts tokens issued by GitHub Actions; the resulting client ID, tenant ID, and subscription ID are stored in the GitHub Environment. For AWS, this means creating an IAM OIDC identity provider and an IAM role with a trust policy that accepts GitHub Actions tokens; the role ARN and region are stored in the GitHub Environment. +The verify workflow reports one of the following failure outcomes in the `verify-cloud-auth-result` artifact, each with a human-readable message and remediation guidance: -##### Step 4: Verify cloud authentication +| Outcome | Meaning | +| --- | --- | +| Required setting missing | A required variable for the selected cloud provider is absent. | +| Cloud provider does not trust GitHub | The cloud provider rejected the GitHub OIDC token (trust policy or federated credential mismatch). | +| Identity lacks required permissions | Authentication succeeded, but the identity lacks the required permissions. | +| Kubernetes cluster cannot be reached | A Kubernetes cluster name was given, but the cluster was not found or its API server is unreachable. | +| Cloud provider cannot be reached | The cloud provider's authentication endpoint could not be reached. | -Before the environment is considered ready, the frontend dispatches the `radius-verify-cloud-auth.yml` workflow (which invokes the `radius-project/verify-cloud-auth` action) for the target GitHub Environment. This confirms that the environment's variables and OIDC configuration are complete and correct, so that misconfiguration is surfaced during setup rather than on the first deployment. +In every case, the frontend is responsible for surfacing the outcome and its remediation guidance to the developer so they can correct the problem; how it presents them is a frontend concern. -The verification performs an end-to-end authentication check: it requests a GitHub Actions OIDC token, exchanges it with the cloud provider, and confirms that the resulting short-lived credentials are valid. It does not provision, modify, or delete any resources. +### User Story 1.2: Deploy my application and view the result -When an EKS or AKS cluster name is provided in the environment, the verification additionally confirms Kubernetes connectivity: it uses the authenticated cloud credentials to retrieve the cluster's access configuration and confirms that the cluster's API server is reachable. This catches a misnamed cluster, a cluster in a different region or resource group, or an identity that lacks access to the cluster before the first deployment. +> As a developer, I want to deploy my application and see detailed information about what was deployed, so that I can trust Radius provisioned and wired everything together correctly. -#### Workflow outcome and failure cases +#### Summary -The outcome is reported in two complementary ways, consistent with how all Repo Radius workflows return results: +With the application already modeled in `app.bicep`, the developer asks the frontend to deploy it. Repo Radius runs on demand inside a GitHub Actions runner, provisions the resources defined in `app.bicep`, connects them to the containers, and shuts down. The developer never provisions a Kubernetes cluster, installs Radius, or learns each provider's resource types and arguments. -* **Workflow run conclusion**. The GitHub Actions run concludes as success or failure, giving the frontend a coarse pass/fail signal it can read directly from the run status via the GitHub Actions API without downloading any files. -* **Result artifact**. The workflow uploads a structured result artifact (a small JSON document) containing the specific outcome from the table below, a human-readable message, and remediation guidance. The frontend downloads this artifact to distinguish between the failure modes (for example, "trust not established" versus "cluster unreachable") and to surface actionable guidance to the user. +#### User Experience -The workflow reports one of the following outcomes: +The developer asks the frontend to deploy an application: -| Outcome | Meaning | Example artifact contents | -| --- | --- | --- | -| Authentication succeeded (`success`) | Authentication succeeded and the credentials are valid. | The `verify-cloud-auth-result` artifact with a message such as "Authentication succeeded. Environment is ready for deployments." | -| Missing configuration (`missing_configuration`) | A required variable for the selected cloud provider is absent. | The `verify-cloud-auth-result` artifact with a message such as "A required cloud authentication variable is not set. Re-run environment and OIDC setup to populate the environment." | -| Trust not established (`trust_not_established`) | The cloud provider rejected the GitHub OIDC token. | The `verify-cloud-auth-result` artifact with a message such as "Cloud provider rejected the GitHub OIDC token. Re-run environment and OIDC setup to verify the IAM role trust policy or app registration federated credential matches the repository, environment, and subject claim." | -| Insufficient permissions (`insufficient_permissions`) | Authentication succeeded but the identity lacks permission to perform the check. | The `verify-cloud-auth-result` artifact with a message such as "Authenticated successfully, but the AWS IAM role or Azure app registration is missing the required permissions. Re-run the environment and OIDC setup or manually add the missing permission. See the Radius documentation for required permissions." | -| Cluster unreachable (`cluster_unreachable`) | A cluster name was provided, but the cluster could not be found or its API server could not be reached. | The `verify-cloud-auth-result` artifact with a message such as "Cluster my-eks-cluster could not be found or its API server is unreachable. Correct the EKS cluster name, or grant the identity access to the cluster." | -| Provider unreachable (`provider_unreachable`) | The cloud provider's authentication endpoint could not be reached. | The `verify-cloud-auth-result` artifact with a message such as "Cloud provider authentication endpoint could not be reached. Re-run the verification workflow to try again. If it persists, check the provider's service status." | +1. **Request the deployment.** The developer asks for the application to be deployed, selecting the environment by name. For example, the developer requests the storefront application to be deployed to the `dev` environment. -The result artifact is uploaded under a well-known, stable artifact name (for example, `verify-cloud-auth-result`) and is available to the frontend via the GitHub Actions API. The artifact name and the JSON schema form part of the stable contract between Repo Radius and any frontend, so they must remain backward-compatible within a major version. +2. **Preview what will be created.** Before anything is provisioned, the frontend shows the cloud resources that will be created and asks the developer for confirmation. For example, since the `dev` environment is configured to deploy to Azure, the frontend shows the developer: -### Scenario 3-4: Deploy a new or updated application + | Application resource | Cloud resource | + | -------------------- | ----------------------------------------------- | + | `frontend` container | Kubernetes service, deployment, service account | + | `api` container | Kubernetes service, deployment, service account | + | `orders-db` database | Azure Database for PostgreSQL Flexible Server | + | `cache` | Azure Cache for Redis | + | `orders-queue` | Azure Event Hubs | -From the developer's point of view, this is the step that matters most: they ask to deploy the storefront to an environment and need to come away knowing it actually worked, or, if it did not, exactly what failed. The frontend turns that intent into a workflow dispatch and reports the result back. + It is the frontend's responsibility to examine the application's resources and their recipes to produce this initial deployment plan; previewing changes to an already-deployed application is covered in User Story 2.2. -#### Prerequisites +3. **Watch the deployment progress.** As the deployment runs, the frontend shows the status of each individual cloud resource. The developer sees the Azure Database for PostgreSQL Flexible Server move from pending to provisioning to succeeded (or failed), so progress is visible resource by resource rather than as a single opaque step. -* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). -* Cloud authentication has been verified for the environment and `verify-cloud-auth-result` reported `success` (Scenario 2). -* The application definition is present in the repository. -* The `radius-run-rad-commands.yml` workflow is committed to the repository (Step 1). +4. **See each resource's identity.** When the deployment completes, the frontend shows the cloud provider resource ID for each provisioned resource. For the storefront's database, this is the Azure resource ID of the Flexible Server, for example `/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/storefront-dev/providers/Microsoft.DBforPostgreSQL/flexibleServers/storefront-orders-db`. -#### Workflow +#### Result -The frontend deploys (or updates) an application by dispatching the `radius-run-rad-commands.yml` workflow (which invokes the `radius-project/run-rad-commands` action) for the target GitHub Environment. Deployment is expressed as a `rad deploy` command in the `rad_commands` input; an update uses the same command against the same environment, and Radius reconciles the change incrementally from the persisted state. The workflow accepts the following inputs: +The frontend deploys the application by dispatching `radius-run-rad-commands.yml` for the target environment with a `rad deploy` command. | Input | Value | | --- | --- | | `environment` | The GitHub Environment name | +| `ref` | The git commit or tag to deploy (defaults to the latest commit on the default branch) | | `rad_commands` | `['deploy .radius/app.bicep', 'app graph storefront -o json']` | -> [!IMPORTANT] -> Repo Radius does not accept arbitrary `rad` invocations. Each entry in `rad_commands` is validated against an enum of allowed commands so that commands which are not applicable to the ephemeral, per-run model (for example, commands that manage or upgrade a persistent control plane, switch workspaces, or change installation state) cannot run, and so the contract stays narrow and reviewable. Validation happens before the control plane is created or any command runs, so a disallowed command errors out and fails fast without provisioning anything. The specific set of allowed commands is defined in the technical design. The enum is part of the stable contract and is versioned with the action; additional commands may be introduced in backward-compatible releases. +Only commands from an allowed set are accepted; each entry in `rad_commands` is validated before anything runs, so commands that do not fit the ephemeral, per-run model (for example, managing a persistent control plane or changing installation state) are rejected up front rather than provisioning anything. -To deploy the storefront and capture its application graph, the frontend dispatches the workflow for the target environment (`production`) with the two `rad` commands shown above in the `rad_commands` input. +On dispatch, the action checks out the repository at the requested `ref` to obtain the application definition, authenticates to the cloud provider using the environment's OIDC variables, then creates a fresh ephemeral Radius control plane on the runner and loads the persisted data store into it. The control plane hosts Radius only; the application's workloads deploy to the environment's own EKS or AKS cluster. The action registers the default recipe pack so the application's resources resolve without the developer authoring recipes, runs the requested `rad` commands in order (stopping at the first failure), and writes the updated data store back so the next operation resumes from it. This per-run control plane lifecycle is common to every `run-rad-commands` operation. -The `radius-run-rad-commands.yml` workflow contains only one action calling the `radius-project/run-rad-commands@v1` GitHub Action which is published to the GitHub Marketplace. This is deliberate: the workflow file lives in the user's repository, where logic is difficult to update because each change requires a commit to every consuming repository. By keeping the workflow as a thin wrapper that simply invokes the published action, all of the real logic lives in the action itself and can be updated centrally on the GitHub Marketplace. Users automatically pick up new backward-compatible releases without any change to the workflow in their repository. The `radius-project/run-rad-commands` action performs the following: +The run performs two commands: `rad deploy` provisions the resources, and a read-only `rad app graph` returns the application graph. The `app graph` output provides each resource's cloud provider resource ID, the connections between components, and the reachable endpoints, which the frontend surfaces so the developer can confirm everything was wired as modeled (User Experience step 4). On success, the action uploads a single `run-rad-commands-result` artifact whose `commands` array holds each command's status and output. On a successful deployment, the action also records a GitHub Deployment for the environment at the deployed commit, so the version running in each environment is tracked in GitHub and can later be promoted to another environment (User Story 3.1). The frontend uses the streaming workflow logs to report incremental progress and the `run-rad-commands-result` artifact to report the final outcome to the developer. -1. **Authenticate with cloud providers**. The workflow uses the GitHub Environment's OIDC variables to authenticate. For Azure, it calls `azure/login` with the client ID, tenant ID, and subscription ID. For AWS, it calls `aws-actions/configure-aws-credentials` with the IAM role ARN and region. +This is the same standard deployment Radius performs today; Repo Radius does not change how Radius deploys applications. The only potential change to core Radius is adjusting the output of the `rad deploy` command so the frontend can collect incremental resource deployment progress updates. -1. **Load the Radius data store**. The workflow reads the persisted Radius data store from the configured storage location and loads it into the ephemeral control plane. On first run, the data store is empty. +#### Exceptions -1. **Create an ephemeral Radius control plane**. The workflow creates a lightweight k3d cluster on the runner, installs the `rad` CLI, and installs Radius on the k3d cluster. This ephemeral cluster hosts the Radius control plane only; application workloads deploy to the developer's target cluster. +The action reports one of the following failure outcomes in the `run-rad-commands-result` artifact: -1. **Create a Radius environment based on the GitHub Environment**. The workflow creates a Radius resource group and environment with the properties from the GitHub Environment passed as an input to the workflow. +| Outcome | Meaning | +| --- | --- | +| Command failed | A command did not complete; the remaining commands did not run, and the failed command's error output is included. | +| Command not allowed | A requested command is not in the allowed set; nothing ran. | +| Environment not found | The named GitHub Environment does not exist or has no configuration; nothing ran. | +| Cloud authentication failed | Cloud authentication failed before any command ran. | +| State could not be loaded | The persisted data store could not be read or restored; nothing ran. | +| State could not be saved | The commands ran, but the updated data store could not be written back, so the cloud may contain resources the persisted state does not reflect. | -1. **Register the default recipe pack**. The workflow registers the default AWS and Azure recipe pack that ships with Radius to the environment, so the application's resources resolve to recipes without the user having to author or register recipes themselves. Customizing the recipe pack is a future feature; the initial release uses the default recipe pack only. +## User Journey 2: Iterate -1. **Execute the `rad` commands**. The workflow runs each command supplied in the `rad_commands` input, in order, prefixing each with `rad`, and captures each command's output. If a command fails, the action stops and does not run the remaining commands. +User Journey 2 covers the developer's day-to-day inner loop once their application is running in an environment: inspecting what is deployed, previewing a change before applying it, redeploying the application as it evolves, and recovering when a deployment fails. Every story builds on the environment and application established in User Journey 1. -1. **Persist the Radius data store**. The workflow writes the updated Radius data store back to the configured storage location so that subsequent workflow runs can resume from the current state. +Except where noted, each operation below is carried out the same way: the frontend dispatches `radius-run-rad-commands.yml` for the target environment with a specific `rad_commands` list (and, for deployments, the git `ref` to deploy), the action runs it through the per-run control plane lifecycle described in User Story 1.2, and it returns the `run-rad-commands-result` artifact with the same failure outcomes listed in that story's Exceptions. Each story below therefore describes only its own `rad_commands` and any behavior unique to it. -#### Workflow outcome and failure cases +### User Story 2.1: View my application's graph -If a `rad` command fails, the action stops before running the remaining commands, so `command_failed` reports a partial result whose final entry is the failed command. `state_persist_failed` is the other partial-state case: the commands ran, but the updated data store could not be written back, so the cloud may contain resources the persisted state does not reflect. The workflow reports one of the following outcomes: +> As a developer, I want to view the graph of my deployed application, so that I can see what cloud resources are deployed, how they are connected, and how to reach them. -| Outcome | Meaning | Example artifact contents | -| --- | --- | --- | -| All commands succeeded (`succeeded`) | Every command ran successfully. | The `run-rad-commands-result` artifact whose `commands` array has an entry per command, each with the command and its output (such as the `rad deploy` output and the JSON application graph). | -| A command failed (`command_failed`) | A command did not complete successfully; remaining commands are not run. | The `run-rad-commands-result` artifact whose last entry is the failed command including its exit code and error output. Entries for commands that did not run are absent. | -| Disallowed command (`disallowed_command`) | One or more commands are not in the allowed-command enum; the request is rejected and no command runs. | The `run-rad-commands-result` artifact identifying the disallowed command. | -| Environment not found (`environment_not_found`) | The named GitHub Environment does not exist or has no configuration. The request is rejected before authentication and no command runs. | The `run-rad-commands-result` artifact with an empty `commands` array and a message such as "GitHub Environment 'production' was not found or has no configuration. Create the environment and complete cloud authentication setup before deploying." | -| Authentication failed (`authentication_failed`) | Cloud authentication failed before any command ran; the GitHub OIDC token could not be exchanged for valid AWS or Azure credentials. | The `run-rad-commands-result` artifact with an empty `commands` array and a message such as "Failed to authenticate to AWS. The IAM role trust policy or permissions may have changed since the environment was verified. Re-run cloud authentication verification." | -| State load failed (`state_load_failed`) | The persisted Radius data store could not be read or restored into the control plane, so no command ran. | The `run-rad-commands-result` artifact with an empty `commands` array and a message indicating the stored state is missing or unreadable. | -| State persist failed (`state_persist_failed`) | Commands ran, but the updated data store could not be written back. The cloud may now contain resources that the persisted state does not reflect. | The `run-rad-commands-result` artifact including the command results plus a message warning that state was not saved, so a subsequent run may not reflect the resources that were just provisioned. | +#### Summary -#### The `run-rad-commands-result` artifact +The developer asks the frontend to show the application graph for an application in an environment. Repo Radius returns the graph as a read-only operation, without provisioning, modifying, or deleting anything. -The `run-rad-commands-result` artifact is the contract between Repo Radius and any frontend, so its name and schema are fixed and versioned with the action. It carries the overall `outcome`, a human-readable `message`, and an ordered `commands` array with one entry per command (the command, its outcome, and its captured output). For example, deploying the storefront and then reading its graph yields an artifact whose `commands` array has two succeeded entries: the `rad deploy` output listing the provisioned resources, and the JSON application graph. +#### User Experience -```json -{ - "outcome": "succeeded", - "message": "All 2 commands succeeded.", - "commands": [ - { - "command": "deploy .radius/app.bicep", "outcome": "succeeded", - "output": "Deployment Complete ..." - }, - { - "command": "app graph storefront -o json", - "outcome": "succeeded", "output": "{ ... }" - } - ] -} -``` +The developer asks to view the graph for a deployed application, selecting the environment by name. For example, the developer requests the graph for the storefront application in the `dev` environment, and the frontend shows: -The complete schema is defined in the technical design. +* The resources that make up the application (the `frontend` and `api` containers, and the `orders-db` database, `cache`, and `orders-queue` backing services) +* The connections between them (the `api` connected to `orders-db`, `cache`, and `orders-queue`) +* Each resource's cloud provider resource ID and reachable endpoints -### Scenario 5: Retrieve the application graph +The graph reflects the state Radius recorded during the most recent operation on that environment; it is not a live query of the cloud and does not detect changes made outside Radius. -#### Prerequisites +#### Result -* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). -* Cloud authentication has been verified for the environment (Scenario 2). -* The application has been deployed to the target environment (Scenario 3-4). +The frontend retrieves the graph from the target environment with a single read-only command: `rad_commands: ['app graph storefront -o json']`. The frontend then shows the graph to the developer. -#### Workflow +#### Exceptions -The frontend retrieves the application graph for a deployed application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app graph` command in the `rad_commands` input. This is a read-only operation: it loads the persisted state and returns the deployed application graph without provisioning, modifying, or deleting any resources, so the data store is unchanged. +If the named application has not been deployed to the environment, the `rad app graph` command fails (the Command failed outcome), which the frontend surfaces as nothing to show. -| Input | Value | -| --- | --- | -| `environment` | The GitHub Environment name | -| `rad_commands` | `['app graph storefront -o json']` | +### User Story 2.2: Review planned changes before deployment -The action runs `rad app graph storefront -o json` and writes the result to the `run-rad-commands-result` artifact; on success, the artifact's single entry contains the JSON of the deployed application graph view. For the storefront, the graph shows the `frontend` and `api` containers, with the `api` connected to the `orders-db` database, the `cache`, and the `orders-queue`. +> As a developer, I want to review the changes a deployment will make before I apply them, so that I can catch unintended or destructive changes before they reach my cloud environment. -#### Workflow outcome and failure cases +#### Summary -The workflow reports the same outcomes as the deploy scenario. +Before redeploying (User Story 2.3), the developer wants to see what a deployment will change: which resources it will add, change, or remove. This is analogous to `terraform plan` or `az deployment group what-if`. Producing a trustworthy plan means comparing the application's desired state (the edited `app.bicep`) against its actual deployed state and predicting how the deployment engine and recipes would resolve the difference between the two, including provider-level effects such as whether a change forces a resource to be replaced. The frontend cannot do this on its own; only Radius, which owns the deployment engine and recipe resolution, can produce an accurate what-if. -### Scenario 6: Delete a deployed application +This preview-before-apply capability is central to deploying with confidence, and it is a significant new feature in its own right that is not specific to Repo Radius: self-hosted Radius needs the same what-if, and both delivery models would consume it. Because of that shared scope, it is sequenced as its own standalone feature specification rather than dropped, and that spec will define the user experience, command surface, and result contract. It is called out here so the iterate journey reflects where it belongs, and left undetailed pending that dedicated spec. -#### Prerequisites +### User Story 2.3: Change my application and redeploy -* The target GitHub Environment exists with its cloud and cluster variables populated (Scenario 1). -* Cloud authentication has been verified for the environment (Scenario 2). -* The application has been deployed to the target environment (Scenario 3-4). +> As a developer, I want to change my application and redeploy it, so that my running environment reflects my latest definition without me managing the individual resource changes. -#### Workflow +#### Summary -The frontend deletes an application by dispatching the same `radius-run-rad-commands.yml` workflow with a `rad app delete` command in the `rad_commands` input. The workflow accepts the same inputs as the deploy scenario: +The developer, working through the frontend, edits `app.bicep` and commits it to the repository (for example, they may add a resource or change a setting), then requests the application to be redeployed to an existing environment. Repo Radius runs the same deploy operation as the initial deployment (User Story 1.2). As with Radius today, deployment is incremental and does not prune: a resource removed from the definition is not deleted by a redeploy. The only explicit teardown today is deleting the entire application (User Story 3.2); automatically pruning individual resources dropped from the definition, as `terraform apply` does, is a core Radius limitation. -| Input | Value | -| --- | --- | -| `environment` | The GitHub Environment name | -| `rad_commands` | `['app delete storefront --yes']` | +#### User Experience + +The redeploy experience is the same as the initial deployment (User Story 1.2). The difference is that only changed resources show activity. For example, if the developer added a full-text search service and changed the `orders-db` size, the redeploy provisions the new Azure AI Search resource and updates the database, while the `frontend` and `api` containers, `cache`, and `orders-queue` are reported as unchanged. + +#### Exceptions + +Because a redeploy may change several resources, a mid-deployment failure (the Command failed outcome) can leave the environment partially updated, with some changes applied and others not. The failed command's error output identifies where the redeployment stopped, and the developer can correct the definition and redeploy to converge, or troubleshoot the failure (User Story 2.4). + +### User Story 2.4: Troubleshoot or recover a failed deployment + +> As a developer, I want to understand why a deployment failed and get back to a good state, so that a failed run doesn't leave me stuck or with half-provisioned infrastructure. + +#### Summary + +When a deployment fails, the developer needs to know what went wrong and how to get back to a good state. Repo Radius adopts core Radius's existing troubleshooting and failure recovery capabilities, along with their limitations. The same errors core Radius reports during a deployment are surfaced to the developer, and recovery follows the pattern Radius uses today: correct the problem and redeploy, or delete and redeploy as a last resort. Repo Radius introduces one new failure mode, discussed below. + +#### User Experience -The action follows the same stages as the deploy scenario, running `rad app delete storefront --yes` to delete the application and its associated cloud resources (the `orders-db` PostgreSQL database, the `cache` Redis instance, and the `orders-queue` RabbitMQ queue). The command's output is included in the single `run-rad-commands-result` artifact. +Repo Radius communicates the existing Radius deployment errors straight through to the developer rather than interpreting or replacing them. When a command fails, its error output is captured and written into the `run-rad-commands-result` artifact under the Command failed outcome, alongside the streaming workflow logs and the recorded graph showing which resources were provisioned before the failure. The developer therefore diagnoses from the same information they would see running `rad` locally, and routes to the appropriate recovery path: -#### Workflow outcome and failure cases +* **Authentication or permissions drift.** If the failure is a cloud authentication or permissions error (for example, the trust or role changed since setup), the developer re-verifies the environment (User Story 1.1), then redeploys. +* **Definition or recipe error.** If a resource failed because of the application definition or a recipe (for example, an invalid setting or an unsupported configuration), the developer corrects `app.bicep` and redeploys to converge (User Story 2.3). +* **Partial or unsaved state.** If a run failed partway through, the developer redeploys to converge the environment, or deletes the application and redeploys from scratch (User Story 3.2) when redeploying cannot bring it back to a good state. -The workflow reports the same outcomes as the deploy scenario. +Diagnosing an application that deployed successfully but is not behaving correctly at runtime is done with the workload cluster's own tooling and is out of scope for Repo Radius. -### Scenario 7: Use an updated version of Repo Radius +#### New failure mode in Repo Radius -The two Repo Radius actions are versioned independently and published to the GitHub Marketplace. The lightweight workflows committed to the user's repository reference each action by its major version tag (for example, `radius-project/run-rad-commands@v1`), so every dispatch automatically picks up the latest backward-compatible release of that major version without the user changing anything. +Because its control plane is ephemeral and its state is loaded at the start of a run and saved at the end rather than living in a persistent store, Repo Radius introduces one failure mode that self-hosted Radius does not have: the recorded state and the cloud can fall out of sync when a run fails to persist its data store. This has two consequences: -The `@v1` actions are expected to be long-lasting. Bug fixes and backward-compatible enhancements ship as new `v1.x` releases under the same `@v1` tag, so the user receives them transparently on the next workflow run. Because there is no persistent control plane, there is nothing for the user to upgrade, monitor, or migrate between releases. +* **Orphaned resources.** If a run provisions resources but then fails to save the updated data store (the State could not be saved outcome), those resources exist in the cloud with no record in the persisted state. The control plane is torn down after the run, so nothing retains knowledge of them. +* **Stale starting state.** Because every run begins from whatever was last persisted, a run that ended without saving leaves the next run's view of the application behind reality before it starts, so it may try to recreate resources that already exist. -When a change would break the established contract (for example, the workflow inputs, the allowed-command set, the result artifact names, or the JSON schema), a new major version of the affected action is published (`@v2`). The existing `@v1` action continues to work unchanged, so existing repositories are unaffected until they opt in. To adopt the new version, the frontend updates the workflow in the repository to reference the new major tag (for example, `radius-project/run-rad-commands@v2`). This is the only situation in which a committed workflow needs to change. +Mitigating this is a Repo Radius responsibility rather than a core Radius one: the load and save around each run must be as reliable as possible, and a failed save must be surfaced clearly so the developer knows the cloud may hold resources the recorded state does not reflect. The design of the externalized data store and its load and save guarantees is covered in the technical design. -## Key investments +## User Journey 3: Operate across environments -### Investment 1: Deployment to an external Kubernetes cluster +User Journey 3 covers managing an application beyond a single environment: promoting a validated version from one environment to another, and removing an application and its infrastructure when it is no longer needed. Both stories use the same `run-rad-commands` mechanism and shared convention introduced in User Journey 2. -Enable Radius to deploy application workloads to an AKS or EKS cluster that is separate from the cluster hosting the Radius control plane. Repo Radius runs the control plane on an ephemeral k3d cluster within the GitHub Actions runner while deploying workloads to the developer's target cluster, so the control plane and workload clusters must be fully decoupled. This capability is delivered by the [Deploy to External AKS and EKS Clusters](../environments/2026-05-external-kubernetes.md) feature, which names the external cluster on the environment's cloud provider block. The method of acquiring Kubernetes API access is to be defined in the technical design. +### User Story 3.1: Promote my application to another environment -### Investment 2: Externalized, portable Radius data store +> As a developer, I want to promote the exact version of my application running in one environment to another, so that what I validated in a lower environment is what runs in production. -Move the Radius data store out of the in-cluster database so that state persists across otherwise stateless runs. At the end of a run the store is exported to GitHub-native storage, and at the start of the next run it is re-imported into the ephemeral control plane. This covers the Radius resource data, application graphs, deployment history, and Terraform state. +#### Summary -### Investment 3: Repo Radius GitHub Actions with a stable contract +Promotion deploys the exact version of the application already running in a source environment (for example, `dev`) to a target environment (for example, `production`), so the target runs the identical definition the source was validated with. There is no dedicated promotion operation in Repo Radius. However, Repo Radius enables the frontend to offer promotion functionality by providing two primitives: -Build and publish the two Repo Radius actions (`verify-cloud-auth` and `run-rad-commands`), each versioned by major tag. Define and maintain a stable, well-documented contract: the action inputs, the workflow run conclusion signal, and result artifacts published under well-known names with a versioned JSON schema (see the `run-rad-commands-result` artifact in Scenario 3-4). This contract lets any frontend drive Repo Radius without coupling to its internals and must remain backward compatible within a major version. +* Repo Radius can deploy a specific git commit passed as the `ref` (User Story 1.2). +* After each successful deployment, Repo Radius records a GitHub Deployment so each environment's deployed version is tracked. -### Investment 4: Lightweight workflows for each action +The frontend performs the promotion by reading the commit currently deployed to the source environment from its GitHub Deployment and passing that commit as the `ref` for the target. Repo Radius itself does not look up what is deployed where. Because a version is identified by its git commit and tracked as a GitHub Deployment, the developer promotes without managing branches, pull requests, or re-pointing files, and the target environment's own configuration is applied so the same definition adapts to each environment it is promoted into. -Provide the thin, ready-to-commit workflow file that pairs with each action (`radius-verify-cloud-auth.yml` and `radius-run-rad-commands.yml`). Each workflow declares its inputs and invokes only the corresponding action at its major version tag, with no Repo Radius logic of its own. The frontend writes these workflows into the user's repository so that GitHub Actions has a dispatchable entry point for each operation, and because the workflows contain no logic they only need to change when the referenced major version is updated. +#### User Experience -### Investment 5: Fast ephemeral control plane startup +The developer asks the frontend to promote an application from a source environment to a target environment. For example, the developer promotes the storefront from `dev` to `production`. From there the experience is the same as an initial deployment (User Story 1.2): the frontend previews what will be deployed, the developer confirms, and progress is shown resource by resource. The difference is that the developer chooses a source environment to promote from rather than a version to deploy; the frontend resolves the version from the source environment automatically. Because `production` carries its own configuration, the promoted definition provisions production-scoped resources without any change to the application definition. -Provision the k3d cluster, install the `rad` CLI, and install Radius quickly enough that the per-run startup cost is acceptable within a GitHub Actions runner. Because the control plane is created and torn down on every operation, startup time is on the critical path for every user-facing action and is the primary determinant of perceived responsiveness. The approach to optimizing control plane startup time is to be defined in the technical design. +#### Result -### Investment 6: Cloud credential integration via OIDC +The frontend reads the source environment's current GitHub Deployment to obtain the commit deployed there, then dispatches `radius-run-rad-commands.yml` for the target environment with that commit as the `ref` and the same deploy command as an initial deployment (User Story 1.2). The action checks out the promoted commit, deploys it against the target environment using that environment's own configuration, and returns the `run-rad-commands-result` artifact. On success, the action records a new GitHub Deployment for the target environment at the promoted commit, so every environment's deployed version is tracked and promotions can chain across a pipeline (for example, `dev` to `test` to `production`). -Integrate with GitHub's OIDC federation to provide short-lived AWS and Azure credentials to the Radius control plane without storing long-lived secrets. This includes the per-run authentication steps, injection of session credentials into the Radius pods for Terraform provider access, and the `verify-cloud-auth` preflight check that validates the environment's configuration before the first deployment. +#### Exceptions -## Key dependencies and risks +Promotion reports the same failure outcomes as an initial deployment (User Story 1.2). In addition, if the source environment has no current GitHub Deployment, nothing has been successfully deployed to it yet, so there is no version to promote and the frontend reports that the source environment is empty. -* **Dependency: External UI**. Repo Radius is designed to be shipped as part of a developer solution which includes an application graph visualization, AI-based modeling of the application, and a user interface to configure environments and visualize deployments. These other components are a dependency and not part of the Repo Radius scope. +### User Story 3.2: Remove my application and its infrastructure -* **Risk: Radius startup time**. Radius must start quickly enough within a GitHub Actions runner that the developer experience feels responsive. If startup takes minutes, the experience degrades significantly. +> As a developer, I want to remove my application and its infrastructure from an environment, so that I can tear down what I no longer need and stop paying for it. -* **Risk: Broad cloud permissions and blast radius**. The IAM role (AWS) or app registration (Azure) assumed via OIDC must hold a significant number of permissions to create and destroy the full range of resources an application may require. A role this broad has a high blast radius: a misbehaving recipe, a compromised workflow, or an over-scoped command could create, modify, or destroy a wide range of cloud resources. Scoping these permissions to the minimum necessary, and isolating them per environment, is an important mitigation to design for. +#### Summary -* **Risk: Recipe and recipe pack backward compatibility**. Because Repo Radius is stateless and provisions on demand, every deployment re-resolves the application's resources against whatever recipe pack version is in effect at that time. Once an application is deployed, it must be assumed that there are live cloud resources backed by a recipe. Recipe and recipe pack versions must therefore be tightly controlled so that a newer recipe version remains backward-compatible with resources provisioned by an earlier version; a breaking recipe change could fail to reconcile, orphan, or inadvertently destroy resources that are already deployed. Recipe and recipe pack versioning needs the same backward-compatibility discipline as the action contract itself. +The developer removes an application from an environment, the explicit teardown referenced in User Story 2.3. The frontend dispatches the delete command `rad_commands: ['app delete storefront --yes']`, which deletes the application and its provisioned cloud resources and saves the updated state. The frontend also marks the environment's GitHub Deployment inactive, so the environment is no longer recorded as running the application. Removal reports the same failure outcomes as User Story 1.2; a mid-deletion failure can leave some resources deleted and others still present, which the developer can retry or troubleshoot (User Story 2.4). -## Alternatives considered +## User Journey 4: Evolve + +### User Story 4.1: Upgrade Repo Radius + +> As a developer, I want to keep Repo Radius up to date, so that I get fixes and improvements without performing manual upgrades. + +Repo Radius ships as the two GitHub Actions (User Story 1.1), published to the GitHub Marketplace and versioned by major tag. The committed workflows reference each action by its major tag (for example, `radius-project/run-rad-commands@v1`), so every run automatically picks up the latest backward-compatible release of that major version without the developer changing anything. + +A breaking change (to the workflow inputs, the allowed-command set, or the result artifact schema) ships as a new major version (`@v2`). The existing `@v1` continues to work, so repositories are unaffected until they opt in. Adopting it is the one case where a committed workflow changes: the frontend updates the tag (for example, to `radius-project/run-rad-commands@v2`). + +### User Story 4.2: Migrate to self-hosted Radius + +> As a developer, I want to move my application from Repo Radius to a self-hosted Radius installation, so that I can grow beyond Repo Radius without rewriting my application. + +Repo Radius runs the same Radius as a self-hosted installation, so the application definition (`app.bicep`) is fully portable: a team that outgrows Repo Radius, for example one that wants a persistent control plane, can deploy the same definition to a self-hosted Radius installation without changing it. This compatibility is guaranteed because the initial release uses only the standard resource types that ship with Radius, so the application depends on nothing unique to Repo Radius. This is the no-lock-in portability promised in the positive user outcome. Migrating the recorded state so a self-hosted installation adopts the already-provisioned cloud resources, rather than re-provisioning them, is not planned. + +## User Journey 5: Tailor Repo Radius for a team + +User Journey 5 covers the platform engineer, not the developer. Both stories are largely out of scope for the initial release and are described here only to frame the intended direction. + +### User Story 5.1: Customize which recipes are used by Repo Radius + +> As a platform engineer, I want to customize the recipes Repo Radius uses, so that developer requests for databases, caches, and queues are provisioned according to my organization's standards. + +The platform engineer wants a developer asking for a database, cache, or queue to get infrastructure that meets organizational standards for naming, tagging, network and security baselines, approved services and SKUs, and cost controls, rather than the out-of-the-box defaults. This requires replacing or extending the recipe pack Repo Radius registers on each run. + +Customizing the recipe pack is out of scope for the initial release (see Non-goals). Every run uses the default AWS and Azure recipe pack that ships with Radius. In the future we imagine a platform engineer specifying an organization-wide recipe pack that Repo Radius applies in place of the default, so a developer's resource request is provisioned to the organization's standards without the developer doing anything differently. Because self-hosted Radius already supports custom recipes, this is mainly a matter of giving Repo Radius a place to reference the platform engineer's recipe pack. + +### User Story 5.2: Predefine environments for developers + +> As a platform engineer, I want to predefine the environments my developers deploy to, so that they inherit approved cloud targets and settings without configuring cloud access themselves. + +The platform engineer wants to define the environments teams are allowed to deploy to once, with the correct cloud accounts, clusters, and guardrails, and have developers simply select from them rather than each configuring their own identity federation and cloud targets (User Story 1.1). + +Because Repo Radius models each environment as a GitHub Environment (User Story 1.1), a platform engineer can already create and govern these environments using GitHub's native environment management and protection rules, and developers deploy to the ones that exist. A first-class experience for predefining a catalog of approved environments that developers select from, with organization-wide governance, is deferred and out of scope for this specification. + +## Appendix 1: Requirements + +The requirements below are a first pass at what the initial release must deliver, grouped by component. This is a starting point for planning, not a final list. + +### Functional requirements + +| ID | Component | Functional requirement | +| --- | --- | --- | +| FR1 | `verify-cloud-auth` action | Shall be published to the Marketplace, versioned by major tag. | +| FR2 | `verify-cloud-auth` action | Shall request a GitHub OIDC token, exchange it with AWS or Azure, confirm the short-lived credentials are valid, and verify the identity's permissions. | +| FR3 | `verify-cloud-auth` action | Shall confirm the workload cluster's API server is reachable when a cluster name is present. | +| FR4 | `verify-cloud-auth` action | Shall publish a `verify-cloud-auth-result` artifact with a versioned JSON schema (outcome, message, remediation) under a stable artifact name. | +| FR5 | `run-rad-commands` action | Shall be published to the Marketplace, versioned by major tag. | +| FR6 | `run-rad-commands` action | Shall check out the repository at the requested `ref` commit or tag before running commands. | +| FR7 | `run-rad-commands` action | Shall validate each entry in `rad_commands` against an allowed-command enum before executing anything. | +| FR8 | `run-rad-commands` action | Shall authenticate to AWS or Azure using the environment's OIDC variables. | +| FR9 | `run-rad-commands` action | Shall create an ephemeral Radius control plane (k3d cluster, `rad`, Radius) and tear it down after the run. | +| FR10 | `run-rad-commands` action | Shall deploy application workloads to the environment's external EKS or AKS cluster while the ephemeral control plane, which hosts Radius only, runs on the runner. | +| FR11 | `run-rad-commands` action | Shall create the Radius resource group and environment from the GitHub Environment variables and register the default recipe pack. | +| FR12 | `run-rad-commands` action | Shall run the requested `rad` commands in order, stop at the first failure, and capture each command's output. | +| FR13 | `run-rad-commands` action | Shall record a GitHub Deployment on a successful deploy and mark it inactive on delete. | +| FR14 | `run-rad-commands` action | Shall publish a `run-rad-commands-result` artifact with a versioned JSON schema and an ordered `commands` array under a stable artifact name. | +| FR15 | `run-rad-commands` action | Shall report a failed data-store save distinctly (the "State could not be saved" outcome) so the developer knows the cloud may hold resources the persisted state does not reflect. | +| FR16 | Slim workflows | Shall be provided as ready-to-commit `radius-verify-cloud-auth.yml` and `radius-run-rad-commands.yml` that invoke the actions at their major tag. | +| FR17 | Radius data store | Shall be externalized to GitHub-native storage, loaded at the start of each run and saved at the end (resource data, application graphs, deployment history, Terraform state). | +| FR18 | Radius CLI | `rad deploy` output shall be adjusted so the frontend can report resource-by-resource progress. | + +**Deferred to a later release:** preview planned changes / what-if (User Story 2.2), prune on deploy (User Story 2.3), custom recipe packs (User Story 5.1), a predefined environment catalog (User Story 5.2), and state migration to self-hosted Radius (User Story 4.2). + +### Non-functional requirements + +| ID | Category | Non-functional requirement | +| --- | --- | --- | +| NFR1 | Performance | Control plane startup shall be fast enough that per-run latency is acceptable, since startup is on the critical path for every operation. | +| NFR2 | Security | Deployments shall authenticate with short-lived, OIDC-issued cloud credentials and shall not store long-lived cloud secrets. | +| NFR3 | Security | The cloud identity shall be scoped to the least privilege required and isolated per environment to limit blast radius. | +| NFR4 | Reliability | The data store load and save around each run shall be reliable and atomic, so a failed run does not silently corrupt or desync state. | + +## Appendix 2: Dependencies and risks + +**Dependency: Frontend.** Repo Radius is a backend; it requires a separate frontend (such as the Copilot app) to turn user intent into workflow dispatches and present results. The frontend is out of scope here but is a hard dependency for an end-to-end experience. + +**Dependency: External workload cluster support.** Deploying workloads to a cluster separate from the ephemeral control plane is delivered by the [Deploy to External AKS and EKS Clusters](../environments/2026-05-external-kubernetes.md) feature, which Repo Radius depends on. + +**Risk: Ephemeral startup time.** The control plane is created and torn down on every operation, so startup is on the critical path for every user-facing action. If it takes minutes, the experience degrades badly, making startup time the primary determinant of perceived responsiveness. + +**Risk: Broad cloud permissions and blast radius.** The OIDC-assumed IAM role (AWS) or app registration (Azure) must hold enough permission to create and destroy the full range of resources an application may need. A role this broad has a high blast radius: a misbehaving recipe, a compromised workflow, or an over-scoped command could affect a wide range of cloud resources. Scoping permissions to the minimum and isolating them per environment is an important mitigation. + +**Risk: Recipe and recipe pack backward compatibility.** Because Repo Radius is stateless and re-resolves resources against whatever recipe pack is in effect at each run, a newer recipe version must remain backward compatible with resources provisioned by an earlier version; a breaking recipe change could fail to apply, orphan, or destroy already-deployed resources. Recipe versioning needs the same discipline as the action contract. + +## Appendix 3: Alternatives considered ### Multiple operation-specific actions versus a single generic action We considered publishing a separate action for each operation (for example, `radius-deploy`, `radius-destroy`, `radius-app-graph`) instead of the single `run-rad-commands` action. -* **Multiple operation-specific actions**. Each action exposes a narrow, strongly typed input surface (for example, `radius-deploy` takes an application path; `radius-destroy` takes an application name), so the contract is self-documenting and each action validates only the inputs relevant to it. The drawback is that every new operation requires a new action, a new workflow file committed to the repository, and a new major-version lifecycle to maintain. Composing several operations in one run (deploy, then read the graph) requires multiple workflow dispatches and multiple ephemeral control plane startups, multiplying the per-run startup cost that Investment 5 works to minimize. +* **Multiple operation-specific actions**. Each action exposes a narrow, strongly typed input surface (for example, `radius-deploy` takes an application path; `radius-destroy` takes an application name), so the contract is self-documenting and each action validates only the inputs relevant to it. The drawback is that every new operation requires a new action, a new workflow file committed to the repository, and a new major-version lifecycle to maintain. Composing several operations in one run (deploy, then read the graph) requires multiple workflow dispatches and multiple ephemeral control plane startups, multiplying the per-run startup cost. * **Single generic action (recommended)**. One action runs an ordered list of allowed `rad` commands. A single ephemeral control plane is created once per run and reused across all commands, so multi-step flows (deploy, then `app graph`) pay the startup cost once. Adding a new operation usually means allowing a new command in the enum rather than publishing a new action and workflow. The trade-off is a less strongly typed input surface, which is mitigated by validating every command against the allowed-command enum before anything runs. **Recommendation**: The single generic `run-rad-commands` action. It minimizes per-run startup cost for multi-step flows, keeps the committed workflow count small, and lets new operations ship as backward compatible additions to the allowed-command enum rather than as new actions. It also reduces the surface area in the user's repository to a single, highly flexible workflow that can accommodate future functionality: only the published action (which lives outside the user's repository) needs to be updated with new allowed commands, so the user's repository does not need to change. @@ -333,14 +434,22 @@ We considered uploading a separate result artifact per command instead of one co * **Per-command artifacts**. Each command's output is uploaded as its own artifact (for example, `rad-command-0`, `rad-command-1`), so the frontend can download a single command's output without retrieving the others. The drawback is that the artifact set varies with the number and order of commands, so the frontend must discover artifact names dynamically, correlating outputs back to the requested order is awkward, and a run produces many small artifacts. * **Single combined artifact (recommended)**. One `run-rad-commands-result` artifact contains a `commands` array with an entry per command, in input order, each with the command, exit code, and output. The artifact name is stable and known in advance, the ordering is explicit, and a single download yields the full result of the run. -**Recommendation**: The single combined `run-rad-commands-result` artifact. A stable, well-known artifact name with an ordered `commands` array is simpler for the frontend to consume and keeps the versioned contract small. +**Recommendation**: The single combined `run-rad-commands-result` artifact. A stable, well-known artifact name with an ordered `commands` array is simpler for the frontend to consume and keeps the contract small. ### Environments defined in a repository file versus GitHub Environments We considered letting the user define multiple environments in a checked-in file (for example, an `env.bicep` in the repository) instead of modeling each environment as a GitHub Environment. -* **Environments defined in a repository file**. A single `env.bicep` declares every environment (dev, test, production) and its cloud provider and cluster details in one place under version control, so the full set of environments is reviewable in a pull request and lives alongside the application. The drawback is that it duplicates a concept GitHub already provides. Cloud credentials must still be stored as GitHub Environment secrets, so an env.bicep would split related configuration across two locations: non-secret details in the file and secrets in the environment. - -* **GitHub Environments (recommended)**. Each environment is a GitHub Environment whose variables hold the cloud provider and cluster details. This reuses GitHub's native environment model including scoped secrets. The downside is that GitHub Environments are not easily created in bulk or declaratively; each environment and its variables are configured imperatively through the GitHub UI, the `gh` CLI, or the REST API rather than from a single checked-in file. The offsetting upside is that users likely already know how to create GitHub Environments and may already have tooling or automation for managing them. +* **Environments defined in a repository file**. A single `env.bicep` declares every environment (dev, test, production) and its cloud provider and Kubernetes cluster details in one place under version control, so the full set of environments is reviewable in a pull request and lives alongside the application. The drawback is that it duplicates a concept GitHub already provides. Cloud credentials must still be stored as GitHub Environment secrets, so an env.bicep would split related configuration across two locations: non-secret details in the file and secrets in the environment. +* **GitHub Environments (recommended)**. Each environment is a GitHub Environment whose variables hold the cloud provider and Kubernetes cluster details. This reuses GitHub's native environment model including scoped secrets. The downside is that GitHub Environments are not easily created in bulk or declaratively; each environment and its variables are configured imperatively through the GitHub UI, the `gh` CLI, or the REST API rather than from a single checked-in file. The offsetting upside is that users likely already know how to create GitHub Environments and may already have tooling or automation for managing them. **Recommendation**: Model environments as GitHub Environments only. It reuses GitHub's native scoping, protection, and OIDC integration rather than reinventing them in a repository file, and it keeps a single source of truth for environment configuration that the workflows and the frontend already consume. + +### Repository-scoped versus per-environment OIDC trust + +We considered scoping the cloud trust relationship to each individual GitHub Environment (matching the environment in the token's subject claim) instead of scoping it to the repository. + +* **Per-environment scope**. The AWS IAM role trust policy or Azure federated credential matches an exact subject such as `repo:my-org/my-repo:environment:production`, giving each environment its own trust and the tightest isolation between environments. The drawback is that every new environment requires a corresponding cloud-side change (a new trust condition or federated credential), which does not scale and, on Azure, quickly reaches the limit on federated credentials per app registration. It also pushes cloud configuration back onto the developer for a routine action (adding an environment) that should be self-service in GitHub. +* **Repository scope (recommended)**. The trust condition matches any subject from the repository (for example, `repo:my-org/my-repo:*`), so a single trust relationship and role cover every environment in the repository. Adding or renaming an environment that targets the same cloud account is purely a GitHub-side action and requires no cloud change. The trade-off is coarser isolation: all environments in the repository can assume the same cloud identity, so separation between non-production and production is achieved by targeting a different cloud account or subscription (each with its own trust and role) rather than by the trust condition alone. + +**Recommendation**: Repository-scoped trust. It keeps adding environments a self-service, GitHub-only action, avoids per-environment cloud configuration and Azure's federated-credential limits, and matches how teams already separate production from non-production, by using a distinct cloud account or subscription. Where stronger isolation between environments in the same account is required, a team can still create a separate role and repository-scoped trust per account. From 85294ca19c6474425cbd143dc4a443adfb52aa74 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Thu, 2 Jul 2026 14:03:27 -0500 Subject: [PATCH 12/17] Small fixes based on feedback Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 184 ++++++++++++------ 1 file changed, 124 insertions(+), 60 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 4041bb0eaa0..c49449fb8a7 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -8,7 +8,7 @@ Today, Radius requires users to install Radius on a Kubernetes cluster. That Kub This is a heavy requirement to place on any team. Running Radius as a Kubernetes service carries real install and maintenance complexity, and a team must take that on before they can build a single application. Teams that already have platform engineers, or their own Kubernetes expertise, may accept that cost. For everyone else, and especially for developers without Kubernetes expertise, it is too much to ask just to build an app. This makes Radius not a viable option for these users. -Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius transforms Radius from a heavyweight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner, with state stored externally in the same GitHub account. +Repo Radius is a rethinking of how to deliver Radius to developers. It transforms Radius from a heavyweight persistent control plane into a lightweight on-demand engine. Concretely, Repo Radius enables developers to run Radius within a GitHub Actions runner, with state stored externally in the same GitHub account. ### Top level goals @@ -21,11 +21,11 @@ Repo Radius is a rethinking of how to deliver Radius to developers. Repo Radius ### Non-goals (out of scope) * Replacing the existing persistent control plane deployment model. Repo Radius is an alternative delivery mechanism, not a replacement. -* Building the frontend UI component such as the Copilot app. Repo Radius must ship with a separate frontend component. +* Building the frontend UI component such as the Radius extension for the Copilot app. Repo Radius must ship with a separate frontend component. * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Supporting multi-repo applications is deferred to a later date. -* Customizing, hardening, or expanding the recipe pack. The initial release uses the default AWS and Azure recipe pack that ships with Radius as-is; improving that pack is separate work, deferred to a later date. +* Customizing, hardening, or expanding the recipe pack. The initial release uses the default AWS and Azure recipe pack that ships with Radius as-is. ## Definition of terms @@ -39,6 +39,8 @@ Throughout this document, several terms are used with a specific meaning in the **Ephemeral control plane** – A Radius control plane created on demand inside a GitHub Actions runner for the duration of a single operation and torn down when it completes. +**k3d cluster** – A lightweight Kubernetes cluster which hosts the ephemeral Radius control plane inside a GitHub Actions runner. + **Workload cluster** – The developer's own EKS or AKS cluster where application workloads are deployed, separate from the ephemeral control plane. **Radius data store** – The persisted Radius state (resource data, application graphs, deployment history, and Terraform state). In self-hosted Radius this is held in etcd. @@ -53,9 +55,9 @@ Throughout this document, several terms are used with a specific meaning in the ### User personas -The primary user is a developer working on a cloud-native application hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They do not have a dedicated platform engineering team and likely do not have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. +The primary user is a developer building a cloud-native application with the source code hosted on GitHub. They may be an individual developer, a small team, or part of a larger organization. They likely do not have a dedicated platform engineering team or have deep Kubernetes expertise. They want to deploy their application to AWS or Azure without managing infrastructure. -A secondary user is a platform engineer who tailors Repo Radius for their teams, customizing recipes and predefining governed environments (User Journey 5) so that developers deploy within approved organizational standards. This tailoring role is largely deferred beyond the initial release. +A secondary user is a platform engineer who tailors Repo Radius for their teams, customizing recipes and predefining governed environments (User Journey 5) so that developers deploy within approved organizational standards. ### The developer's problem @@ -68,21 +70,21 @@ I am a developer building a cloud-native application, an online storefront: a we ### The platform engineer's problem -I am a platform engineer supporting several teams. I want my developers to deploy on their own, but I am accountable for what lands in our cloud accounts. The default recipes are a good start but do not match our standards for naming, tagging, network and security baselines, approved services and SKUs, and cost controls; and today each repository configures its own cloud access and environments, which I cannot easily govern. I want to set recipes and approved environments once and have every team inherit them, so developers keep their fast, self-service experience while I keep the consistency, security, and governance I am accountable for. +I am a platform engineer supporting several teams. I want my developers to deploy on their own, but I am accountable for what lands in our cloud accounts. I need to keep the consistency, security, and governance I am accountable for without taking away the fast, self-service experience my developers depend on. I do this today by publishing standard internally developed Terraform modules that all projects are expected to use and programmatically provisioning cloud environments via a self-service developer portal such as Backstage. -* **No way to enforce organizational standards**. The default recipes do not match the organization's naming, tagging, network and security baselines, approved services and SKUs, or cost controls, so ungoverned deployments produce non-compliant resources that must be found and remediated after the fact. -* **Ungoverned, per-repository environments**. Each repository configures its own identity federation and targets whatever cloud account, region, and Kubernetes cluster a developer chooses, leaving the platform engineer without central visibility or assurance that production is separated from non-production. -* **No central place to tailor Repo Radius**. There is no single place to customize recipes and predefine approved environments once and have every team inherit them. +* **Predefined environments and managed credentials**. The platform engineer needs a way to programmatically create logical environments teams deploy to (dev, test, production), each bound to an approved cloud account, region, and cluster, and to manage the cloud credentials centrally so developers never handle long-lived secrets and production stays separated from non-production. +* **Standard, enforceable deployment modules**. The platform engineer needs to define the organization's standard IaC deployment modules once, encoding naming, tagging, network and security baselines, approved services and SKUs, and cost controls, and enforce them so every team's resources are provisioned to those standards rather than remediated after the fact. +* **Scale across a large estate**. The platform engineer needs both of these to work at scale across many teams and repositories, so governance is defined once and inherited everywhere without per-team configuration or after-the-fact enforcement. ### Positive user outcome -A developer can deploy to AWS or Azure without installing Kubernetes or Radius: they create environments with their cloud account details, and Repo Radius provisions the infrastructure on demand within GitHub Actions, storing state in the same GitHub account. If they outgrow it, the same application definition moves to a self-hosted Radius installation unchanged. +A developer can deploy to AWS or Azure without installing Kubernetes or self-hosted Radius. They create environments with their cloud account details and ask for their application to be deployed and Repo Radius provisions the exact infrastructure needed for the application in a secure and cost-efficient manner. As they modify their application, Repo Radius updates the deployed infrastructure to match the updated application. If they outgrow Repo Radius, the developer can migrate to self-hosted Radius without changing their application definition. -A platform engineer sets organizational recipes and governed environments once and every team inherits them, so developers keep a fast, self-service experience while the platform engineer keeps the consistency, security, and governance they are accountable for. +A platform engineer can define organization-wide recipes once and every team inherits them, and is able to programmatically create environments with cloud credentials so developers keep a fast, self-service experience while the platform engineer keeps the consistency, security, and governance they are accountable for. ## User journeys -The experience is organized into five journeys. The first four follow the developer's lifecycle of an application: getting it running, iterating on it, operating it across environments, and evolving it over time. The fifth covers the platform engineer who tailors Repo Radius for a team. Each journey is made up of user stories that describe a single thing the user wants to accomplish. The journeys and their stories are detailed in the sections that follow. +The user experience is organized into five journeys. The first four follow the developer's lifecycle of an application: getting it running, iterating on it, operating it across environments, and evolving it over time. The fifth covers the platform engineer who tailors Repo Radius for a team. This fifth user journey is largely deferred beyond the initial release. Each journey is made up of user stories that describe a single thing the user wants to accomplish. The journeys and their stories are detailed in the sections that follow. Repo Radius is the backend for these journeys, not a user interface. A separate frontend (such as the Copilot app) turns the user's intent into GitHub Action workflow runs and shows the results. The user stories describe what the user wants to accomplish and what Repo Radius does on each operation; what the user sees and clicks is defined by the frontend. @@ -104,13 +106,13 @@ The developer chooses where the application should run (an AWS account and regio The developer works entirely through the frontend, which walks them through three steps: -1. **Enable Repo Radius for the repository.** The frontend asks the developer for permission to enable Radius for the repository. On the developer's acknowledgement, it commits the required GitHub Action workflows to the repository so that GitHub Actions has a dispatchable entry point for each Radius operation. +1. **Enable Repo Radius for the repository.** The frontend asks the developer for permission to enable Radius for the repository. With the developer's acknowledgement, it commits the required GitHub Action workflows to the repository so that GitHub Actions has a dispatchable entry point for each Radius operation. -2. **Create an environment.** The frontend guides the developer through creating an environment, prompting for the environment name (for example, `dev`), whether it targets AWS or Azure, the cloud account details (AWS account and region, or Azure subscription and resource group), and the workload cluster details (the EKS or AKS cluster name and Kubernetes namespace). +2. **Create an environment.** The frontend guides the developer through creating a GitHub Environment, prompting for the environment name (for example, `dev`), whether it targets AWS or Azure, the cloud account details (AWS account and region, or Azure subscription and resource group), and the workload cluster details (the EKS or AKS cluster name and Kubernetes namespace). -3. **Set up cloud access.** The frontend guides the developer through establishing the OIDC trust connection between GitHub and their cloud provider. The developer follows the prompts to create the trust on their cloud account, and the frontend captures the resulting identifiers. +3. **Set up cloud access.** The frontend guides the developer through establishing the OIDC trust connection between GitHub and their cloud provider. The developer follows the prompts to create the trust on their cloud account, and the frontend captures the OIDC identifiers and stores them in the GitHub Environment. -Once these steps are complete, the frontend verifies the connection on the developer's behalf and reports whether the environment is ready. +Once these steps are complete, the frontend asks Repo Radius to verify the connection and reports to the user whether the environment is ready. #### Result @@ -127,9 +129,11 @@ Completing these steps produces the following, all stored in the developer's own * **An OIDC trust configuration** on the cloud provider: for Azure, a federated credential on an Entra ID app registration; for AWS, an IAM OIDC identity provider and an IAM role whose trust policy accepts GitHub Actions tokens. The trust is scoped to the repository (matching a subject such as `repo:my-org/my-repo:*`) rather than to an individual environment, so every environment in the repository that targets this cloud account shares it. -To confirm the environment is ready, the frontend dispatches `radius-verify-cloud-auth.yml`, which runs the `radius-project/verify-cloud-auth` action. The action requests a GitHub Actions OIDC token, exchanges it with the cloud provider, confirms the resulting short-lived credentials are valid, and verifies the identity holds the required permissions. When a Kubernetes cluster name is present, it also confirms the cluster's API server is reachable. It does not provision, modify, or delete anything. +To confirm the environment is ready, the frontend dispatches `radius-verify-cloud-auth.yml`, which runs the `radius-project/verify-cloud-auth` action. The action requests a GitHub Actions OIDC token, exchanges it with the cloud provider, confirms the resulting short-lived credentials are valid, and verifies the identity holds the required permissions. When a Kubernetes cluster name is present in the environment, it also confirms the cluster's API server is reachable. It does not provision, modify, or delete anything. + +The action creates a `verify-cloud-auth-result` artifact within GitHub Actions containing the verification result. This artifact is a small JSON document with the outcome of the action and a human-readable message with helpful remediation guidance when there is an error. -The frontend alerts the developer by examining the verify workflow: it reads the run's success or failure conclusion for a coarse signal, and downloads the `verify-cloud-auth-result` artifact, a small JSON document with the specific outcome, a human-readable message, and remediation guidance. The artifact name and schema are a stable contract that the frontend can rely on. On `success`, the frontend reports that the environment is ready, and it is reused by every later deployment. +The frontend informs the developer of the results by reading the workflow's success or failure conclusion for a coarse signal, then calling the GitHub Actions API and examining the artifact for specifics. The artifact name and schema are a stable contract that the frontend can rely on. On `success`, the frontend reports that the environment is ready, and it is reused by every later deployment. Because this trust is scoped to the repository and stores no secret, adding or renaming an environment that targets the same cloud account needs no cloud-side change, and there are no credentials to rotate. It changes in only three cases: the repository is renamed or moved, a new cloud account or subscription is targeted, or the identity's permissions must change. Authentication problems from later drift are handled in the troubleshooting flow (User Story 2.4). @@ -139,13 +143,13 @@ The verify workflow reports one of the following failure outcomes in the `verify | Outcome | Meaning | | --- | --- | -| Required setting missing | A required variable for the selected cloud provider is absent. | +| Required setting missing | A required variable for the selected cloud provider is absent such as AWS region or Azure resource group. | | Cloud provider does not trust GitHub | The cloud provider rejected the GitHub OIDC token (trust policy or federated credential mismatch). | -| Identity lacks required permissions | Authentication succeeded, but the identity lacks the required permissions. | -| Kubernetes cluster cannot be reached | A Kubernetes cluster name was given, but the cluster was not found or its API server is unreachable. | -| Cloud provider cannot be reached | The cloud provider's authentication endpoint could not be reached. | +| Identity lacks required permissions | Authentication succeeded, but the identity lacks the required permissions. Note that the required permissions will need to be documented for the user. | +| Kubernetes cluster cannot be reached | A Kubernetes workload cluster name was given, but the cluster was not found, or its API server is unreachable. | +| Cloud provider cannot be reached | The cloud provider's API endpoint could not be reached. | -In every case, the frontend is responsible for surfacing the outcome and its remediation guidance to the developer so they can correct the problem; how it presents them is a frontend concern. +In every case, the frontend is responsible for surfacing the outcome and its remediation guidance to the developer so they can correct the problem. ### User Story 1.2: Deploy my application and view the result @@ -153,7 +157,7 @@ In every case, the frontend is responsible for surfacing the outcome and its rem #### Summary -With the application already modeled in `app.bicep`, the developer asks the frontend to deploy it. Repo Radius runs on demand inside a GitHub Actions runner, provisions the resources defined in `app.bicep`, connects them to the containers, and shuts down. The developer never provisions a Kubernetes cluster, installs Radius, or learns each provider's resource types and arguments. +With the application already modeled in `app.bicep`, the developer asks the frontend to deploy it. Repo Radius runs on demand inside a GitHub Actions runner, provisions the resources defined in `app.bicep` and shuts down. The developer never installs or configures Radius or learns each provider's resource types and arguments. #### User Experience @@ -163,13 +167,13 @@ The developer asks the frontend to deploy an application: 2. **Preview what will be created.** Before anything is provisioned, the frontend shows the cloud resources that will be created and asks the developer for confirmation. For example, since the `dev` environment is configured to deploy to Azure, the frontend shows the developer: - | Application resource | Cloud resource | - | -------------------- | ----------------------------------------------- | - | `frontend` container | Kubernetes service, deployment, service account | - | `api` container | Kubernetes service, deployment, service account | - | `orders-db` database | Azure Database for PostgreSQL Flexible Server | - | `cache` | Azure Cache for Redis | - | `orders-queue` | Azure Event Hubs | + | Application resource | Cloud resource | + | -------------------------- | ----------------------------------------------- | + | `frontend` container | Kubernetes service, deployment, service account | + | `api` container | Kubernetes service, deployment, service account | + | `orders-db` database | Azure Database for PostgreSQL Flexible Server | + | `cache` Redis cache | Azure Cache for Redis | + | `orders-queue` Kafka queue | Azure Event Hubs | It is the frontend's responsibility to examine the application's resources and their recipes to produce this initial deployment plan; previewing changes to an already-deployed application is covered in User Story 2.2. @@ -179,21 +183,21 @@ The developer asks the frontend to deploy an application: #### Result -The frontend deploys the application by dispatching `radius-run-rad-commands.yml` for the target environment with a `rad deploy` command. +The frontend deploys the application by dispatching the `radius-run-rad-commands.yml` workflow passing in the target environment name and the `rad deploy` command. | Input | Value | | --- | --- | | `environment` | The GitHub Environment name | -| `ref` | The git commit or tag to deploy (defaults to the latest commit on the default branch) | +| `ref` | The git commit or tag to deploy (defaults to the latest commit on the default branch). Not used in this user story but is used in User Story 3.1. | | `rad_commands` | `['deploy .radius/app.bicep', 'app graph storefront -o json']` | -Only commands from an allowed set are accepted; each entry in `rad_commands` is validated before anything runs, so commands that do not fit the ephemeral, per-run model (for example, managing a persistent control plane or changing installation state) are rejected up front rather than provisioning anything. +Only commands from an allowed set are accepted; each entry in `rad_commands` is validated before anything runs, so commands that do not fit the ephemeral, per-run model (for example, managing Radius resource groups or workspaces) are rejected up front rather than executing the workflow. -On dispatch, the action checks out the repository at the requested `ref` to obtain the application definition, authenticates to the cloud provider using the environment's OIDC variables, then creates a fresh ephemeral Radius control plane on the runner and loads the persisted data store into it. The control plane hosts Radius only; the application's workloads deploy to the environment's own EKS or AKS cluster. The action registers the default recipe pack so the application's resources resolve without the developer authoring recipes, runs the requested `rad` commands in order (stopping at the first failure), and writes the updated data store back so the next operation resumes from it. This per-run control plane lifecycle is common to every `run-rad-commands` operation. +On dispatch, the action checks out the repository at the requested `ref` (or the latest commit if not specified) to obtain the application definition, authenticates to the cloud provider using the environment's OIDC variables, then creates a fresh ephemeral Radius control plane on the runner and loads the persisted data store into it. The control plane hosts Radius only; the application is deployed to the environment's own EKS or AKS cluster. The action uses the default recipe pack so the application's resources resolve without the developer authoring recipes, runs the requested `rad` commands in order (stopping at the first failure), and writes the updated data store back so the next operation resumes from it. This per-run control plane lifecycle is common to every `run-rad-commands` operation. -The run performs two commands: `rad deploy` provisions the resources, and a read-only `rad app graph` returns the application graph. The `app graph` output provides each resource's cloud provider resource ID, the connections between components, and the reachable endpoints, which the frontend surfaces so the developer can confirm everything was wired as modeled (User Experience step 4). On success, the action uploads a single `run-rad-commands-result` artifact whose `commands` array holds each command's status and output. On a successful deployment, the action also records a GitHub Deployment for the environment at the deployed commit, so the version running in each environment is tracked in GitHub and can later be promoted to another environment (User Story 3.1). The frontend uses the streaming workflow logs to report incremental progress and the `run-rad-commands-result` artifact to report the final outcome to the developer. +The run performs two commands: `rad deploy` provisions the resources, and `rad app graph` returns the application graph. The `rad app graph` output provides each resource's cloud provider resource ID, the connections between components, and the reachable endpoints, which the frontend surfaces so the developer can confirm everything was wired as modeled (User Experience step 4). On success, the action uploads a single `run-rad-commands-result` artifact whose `commands` array holds the output of the deployment and the application graph JSON (see Appendix 4 for an example). On a successful deployment, the action also records a GitHub Deployment for the environment at the deployed commit, so the version running in each environment is tracked in GitHub and can later be promoted to another environment (User Story 3.1). The frontend uses the streaming workflow logs to report incremental progress and the `run-rad-commands-result` artifact to report the final outcome to the developer. -This is the same standard deployment Radius performs today; Repo Radius does not change how Radius deploys applications. The only potential change to core Radius is adjusting the output of the `rad deploy` command so the frontend can collect incremental resource deployment progress updates. +This is the same standard deployment Radius performs today; Repo Radius does not change how Radius deploys applications. The only potential change to core Radius is adjusting the output of the `rad deploy` command so the frontend can collect incremental deployment progress updates. #### Exceptions @@ -205,14 +209,14 @@ The action reports one of the following failure outcomes in the `run-rad-command | Command not allowed | A requested command is not in the allowed set; nothing ran. | | Environment not found | The named GitHub Environment does not exist or has no configuration; nothing ran. | | Cloud authentication failed | Cloud authentication failed before any command ran. | -| State could not be loaded | The persisted data store could not be read or restored; nothing ran. | +| State could not be loaded | The persisted data store could not be read or restored; nothing ran. Note that this does not apply if this is the first deployment (no data store is expected then). Repo Radius will need a method of determining if a data store should be expected or not. | | State could not be saved | The commands ran, but the updated data store could not be written back, so the cloud may contain resources the persisted state does not reflect. | ## User Journey 2: Iterate User Journey 2 covers the developer's day-to-day inner loop once their application is running in an environment: inspecting what is deployed, previewing a change before applying it, redeploying the application as it evolves, and recovering when a deployment fails. Every story builds on the environment and application established in User Journey 1. -Except where noted, each operation below is carried out the same way: the frontend dispatches `radius-run-rad-commands.yml` for the target environment with a specific `rad_commands` list (and, for deployments, the git `ref` to deploy), the action runs it through the per-run control plane lifecycle described in User Story 1.2, and it returns the `run-rad-commands-result` artifact with the same failure outcomes listed in that story's Exceptions. Each story below therefore describes only its own `rad_commands` and any behavior unique to it. +Except where noted, each operation below dispatches `radius-run-rad-commands.yml` through the same per-run lifecycle and failure outcomes described in User Story 1.2, so each story covers only its own `rad_commands` and any unique behavior. ### User Story 2.1: View my application's graph @@ -220,11 +224,11 @@ Except where noted, each operation below is carried out the same way: the fronte #### Summary -The developer asks the frontend to show the application graph for an application in an environment. Repo Radius returns the graph as a read-only operation, without provisioning, modifying, or deleting anything. +The developer asks the frontend to show the application graph for an application in an environment. Repo Radius returns the graph in the `run-rad-commands-result` artifact. #### User Experience -The developer asks to view the graph for a deployed application, selecting the environment by name. For example, the developer requests the graph for the storefront application in the `dev` environment, and the frontend shows: +The developer asks to view the graph for a deployed application, selecting the environment and application by name. For example, the developer requests the graph for the storefront application in the `dev` environment, and the frontend shows: * The resources that make up the application (the `frontend` and `api` containers, and the `orders-db` database, `cache`, and `orders-queue` backing services) * The connections between them (the `api` connected to `orders-db`, `cache`, and `orders-queue`) @@ -242,13 +246,13 @@ If the named application has not been deployed to the environment, the `rad app ### User Story 2.2: Review planned changes before deployment -> As a developer, I want to review the changes a deployment will make before I apply them, so that I can catch unintended or destructive changes before they reach my cloud environment. +> As a developer, I want to review the changes a deployment will make to an already deployed application before I apply them, so that I can catch unintended or destructive changes before they reach my cloud environment. #### Summary Before redeploying (User Story 2.3), the developer wants to see what a deployment will change: which resources it will add, change, or remove. This is analogous to `terraform plan` or `az deployment group what-if`. Producing a trustworthy plan means comparing the application's desired state (the edited `app.bicep`) against its actual deployed state and predicting how the deployment engine and recipes would resolve the difference between the two, including provider-level effects such as whether a change forces a resource to be replaced. The frontend cannot do this on its own; only Radius, which owns the deployment engine and recipe resolution, can produce an accurate what-if. -This preview-before-apply capability is central to deploying with confidence, and it is a significant new feature in its own right that is not specific to Repo Radius: self-hosted Radius needs the same what-if, and both delivery models would consume it. Because of that shared scope, it is sequenced as its own standalone feature specification rather than dropped, and that spec will define the user experience, command surface, and result contract. It is called out here so the iterate journey reflects where it belongs, and left undetailed pending that dedicated spec. +This preview-before-apply capability is central to deploying with confidence, and it is a significant new feature that is not specific to Repo Radius—self-hosted Radius needs the same what-if, and both versions of Radius would use it. Because it is a generalized feature, a standalone feature specification is required that defines the user experience and functionality. It is called out here so the iterate journey reflects where it belongs, and left undetailed pending that dedicated spec. ### User Story 2.3: Change my application and redeploy @@ -256,15 +260,15 @@ This preview-before-apply capability is central to deploying with confidence, an #### Summary -The developer, working through the frontend, edits `app.bicep` and commits it to the repository (for example, they may add a resource or change a setting), then requests the application to be redeployed to an existing environment. Repo Radius runs the same deploy operation as the initial deployment (User Story 1.2). As with Radius today, deployment is incremental and does not prune: a resource removed from the definition is not deleted by a redeploy. The only explicit teardown today is deleting the entire application (User Story 3.2); automatically pruning individual resources dropped from the definition, as `terraform apply` does, is a core Radius limitation. +The developer, working through the frontend, edits app.bicep and commits it to the repository (for example, they may add a resource or change a setting), then requests the application to be redeployed to an existing environment. Repo Radius runs the same deploy operation as the initial deployment (User Story 1.2). As with Radius today, deployment is incremental and does not delete resources removed from the application definition. The frontend is responsible for identifying when resources are removed and prompting the developer if they want to delete the individual resource. Note that Terraform automatically deletes resources after being removed from a definition and the `az` CLI offers the `--mode Complete` to accomplish the same. This is a limitation of Radius today. #### User Experience -The redeploy experience is the same as the initial deployment (User Story 1.2). The difference is that only changed resources show activity. For example, if the developer added a full-text search service and changed the `orders-db` size, the redeploy provisions the new Azure AI Search resource and updates the database, while the `frontend` and `api` containers, `cache`, and `orders-queue` are reported as unchanged. +The redeploy experience is the same as the initial deployment (User Story 1.2). The difference is that only changed resources show activity. For example, if the developer added a full-text search service to the storefront application and changed the `orders-db` size, the redeploy provisions the new Azure AI Search resource and updates the database, while the `frontend` and `api` containers, `cache`, and `orders-queue` are reported as unchanged. #### Exceptions -Because a redeploy may change several resources, a mid-deployment failure (the Command failed outcome) can leave the environment partially updated, with some changes applied and others not. The failed command's error output identifies where the redeployment stopped, and the developer can correct the definition and redeploy to converge, or troubleshoot the failure (User Story 2.4). +Because a redeploy may change several resources, a mid-deployment failure (the Command failed outcome) can leave the environment partially updated, with some changes applied and others not. The failed command's error output identifies where the redeployment stopped, and the developer can correct the definition and redeploy to converge or troubleshoot the failure (User Story 2.4). ### User Story 2.4: Troubleshoot or recover a failed deployment @@ -272,15 +276,15 @@ Because a redeploy may change several resources, a mid-deployment failure (the C #### Summary -When a deployment fails, the developer needs to know what went wrong and how to get back to a good state. Repo Radius adopts core Radius's existing troubleshooting and failure recovery capabilities, along with their limitations. The same errors core Radius reports during a deployment are surfaced to the developer, and recovery follows the pattern Radius uses today: correct the problem and redeploy, or delete and redeploy as a last resort. Repo Radius introduces one new failure mode, discussed below. +When a deployment fails, the developer needs to know what went wrong and how to get back to a good state. Repo Radius adopts core Radius's existing troubleshooting and failure recovery capabilities, along with its limitations. The same errors core Radius reports during a deployment are surfaced to the developer, and recovery follows the pattern Radius uses today: correct the problem and redeploy or delete and redeploy as a last resort. Repo Radius introduces one new failure mode, discussed below. #### User Experience -Repo Radius communicates the existing Radius deployment errors straight through to the developer rather than interpreting or replacing them. When a command fails, its error output is captured and written into the `run-rad-commands-result` artifact under the Command failed outcome, alongside the streaming workflow logs and the recorded graph showing which resources were provisioned before the failure. The developer therefore diagnoses from the same information they would see running `rad` locally, and routes to the appropriate recovery path: +Repo Radius communicates the existing Radius deployment errors straight through to the developer rather than interpreting or replacing them. When a command fails, its error output is captured and written into the `run-rad-commands-result` artifact showing the exact Radius error message. The developer therefore diagnoses from the same information they would see running `rad` locally, and routes to the appropriate recovery path: -* **Authentication or permissions drift.** If the failure is a cloud authentication or permissions error (for example, the trust or role changed since setup), the developer re-verifies the environment (User Story 1.1), then redeploys. -* **Definition or recipe error.** If a resource failed because of the application definition or a recipe (for example, an invalid setting or an unsupported configuration), the developer corrects `app.bicep` and redeploys to converge (User Story 2.3). -* **Partial or unsaved state.** If a run failed partway through, the developer redeploys to converge the environment, or deletes the application and redeploys from scratch (User Story 3.2) when redeploying cannot bring it back to a good state. +* **Authentication or permissions drift.** If the failure is a cloud authentication or permissions error (for example, the trust or role changed since setup), the developer re-verifies the environment (User Story 1.1) which describes OIDC authentication and authorization errors in more detail, then redeploys. +* **Definition or recipe error.** If a resource failed because of the application definition or a recipe (for example, an invalid setting or an unsupported configuration), the developer corrects `app.bicep` and redeploys (User Story 2.3). +* **Partial or unsaved state.** If a run failed partway through, the developer redeploys to converge the environment or, when redeploying cannot bring it back to a good state, deletes the application and redeploys from scratch (User Story 3.2). Diagnosing an application that deployed successfully but is not behaving correctly at runtime is done with the workload cluster's own tooling and is out of scope for Repo Radius. @@ -291,11 +295,11 @@ Because its control plane is ephemeral and its state is loaded at the start of a * **Orphaned resources.** If a run provisions resources but then fails to save the updated data store (the State could not be saved outcome), those resources exist in the cloud with no record in the persisted state. The control plane is torn down after the run, so nothing retains knowledge of them. * **Stale starting state.** Because every run begins from whatever was last persisted, a run that ended without saving leaves the next run's view of the application behind reality before it starts, so it may try to recreate resources that already exist. -Mitigating this is a Repo Radius responsibility rather than a core Radius one: the load and save around each run must be as reliable as possible, and a failed save must be surfaced clearly so the developer knows the cloud may hold resources the recorded state does not reflect. The design of the externalized data store and its load and save guarantees is covered in the technical design. +Mitigating this is a Repo Radius responsibility: the load and save around each run must be as reliable as possible, and a failed save must be surfaced clearly so the developer knows the cloud may hold resources the recorded state does not reflect. The design of the externalized data store and its load and save guarantees is covered in the technical design. ## User Journey 3: Operate across environments -User Journey 3 covers managing an application beyond a single environment: promoting a validated version from one environment to another, and removing an application and its infrastructure when it is no longer needed. Both stories use the same `run-rad-commands` mechanism and shared convention introduced in User Journey 2. +User Journey 3 covers managing an application beyond a single environment: promoting a validated version from one environment to another and removing an application and its infrastructure when it is no longer needed. Both stories use the same `run-rad-commands` mechanism and shared convention introduced in User Journey 2. ### User Story 3.1: Promote my application to another environment @@ -312,11 +316,11 @@ The frontend performs the promotion by reading the commit currently deployed to #### User Experience -The developer asks the frontend to promote an application from a source environment to a target environment. For example, the developer promotes the storefront from `dev` to `production`. From there the experience is the same as an initial deployment (User Story 1.2): the frontend previews what will be deployed, the developer confirms, and progress is shown resource by resource. The difference is that the developer chooses a source environment to promote from rather than a version to deploy; the frontend resolves the version from the source environment automatically. Because `production` carries its own configuration, the promoted definition provisions production-scoped resources without any change to the application definition. +The developer asks the frontend to promote an application from a source environment to a target environment. For example, the developer promotes the storefront from `dev` to `production`. From there the experience is the same as an initial deployment (User Story 1.2): the frontend previews what will be deployed, the developer confirms, and progress is shown resource by resource. The difference is that the developer chooses a source environment to promote from rather than a version to deploy. Because `production` carries its own configuration, the promoted definition provisions production-scoped resources without any change to the application definition. #### Result -The frontend reads the source environment's current GitHub Deployment to obtain the commit deployed there, then dispatches `radius-run-rad-commands.yml` for the target environment with that commit as the `ref` and the same deploy command as an initial deployment (User Story 1.2). The action checks out the promoted commit, deploys it against the target environment using that environment's own configuration, and returns the `run-rad-commands-result` artifact. On success, the action records a new GitHub Deployment for the target environment at the promoted commit, so every environment's deployed version is tracked and promotions can chain across a pipeline (for example, `dev` to `test` to `production`). +The frontend reads the source environment's current GitHub Deployment to obtain the commit deployed there, then dispatches `radius-run-rad-commands.yml` for the target environment with that commit as the `ref` and the same deploy command as an initial deployment (User Story 1.2). The action checks out the promoted commit, deploys it against the target environment using that environment's own configuration, and returns the `run-rad-commands-result` artifact. On success, the action records a new GitHub Deployment for the target environment at the promoted commit, so every environment's deployed version is tracked, and promotions can chain across a pipeline (for example, `dev` to `test` to `production`). #### Exceptions @@ -328,7 +332,7 @@ Promotion reports the same failure outcomes as an initial deployment (User Story #### Summary -The developer removes an application from an environment, the explicit teardown referenced in User Story 2.3. The frontend dispatches the delete command `rad_commands: ['app delete storefront --yes']`, which deletes the application and its provisioned cloud resources and saves the updated state. The frontend also marks the environment's GitHub Deployment inactive, so the environment is no longer recorded as running the application. Removal reports the same failure outcomes as User Story 1.2; a mid-deletion failure can leave some resources deleted and others still present, which the developer can retry or troubleshoot (User Story 2.4). +The developer asks the frontend to delete an application from an environment. The frontend dispatches the delete command `rad_commands: ['app delete storefront --yes']`, which deletes the application and its provisioned cloud resources. The frontend also marks the environment's GitHub Deployment inactive, so the environment is no longer recorded as running the application. Removal reports the same failure outcomes as User Story 1.2; a mid-deletion failure can leave some resources deleted and others still present, which the developer can retry or troubleshoot (User Story 2.4). ## User Journey 4: Evolve @@ -338,13 +342,13 @@ The developer removes an application from an environment, the explicit teardown Repo Radius ships as the two GitHub Actions (User Story 1.1), published to the GitHub Marketplace and versioned by major tag. The committed workflows reference each action by its major tag (for example, `radius-project/run-rad-commands@v1`), so every run automatically picks up the latest backward-compatible release of that major version without the developer changing anything. -A breaking change (to the workflow inputs, the allowed-command set, or the result artifact schema) ships as a new major version (`@v2`). The existing `@v1` continues to work, so repositories are unaffected until they opt in. Adopting it is the one case where a committed workflow changes: the frontend updates the tag (for example, to `radius-project/run-rad-commands@v2`). +A breaking change (to the workflow inputs, the allowed command set, or the result artifact schema) ships as a new major version (`@v2`). The existing `@v1` continues to work, so repositories are unaffected until they opt in. Adopting a new major version requires the user, or the frontend to update the tag in the workflow stored in the repository. ### User Story 4.2: Migrate to self-hosted Radius > As a developer, I want to move my application from Repo Radius to a self-hosted Radius installation, so that I can grow beyond Repo Radius without rewriting my application. -Repo Radius runs the same Radius as a self-hosted installation, so the application definition (`app.bicep`) is fully portable: a team that outgrows Repo Radius, for example one that wants a persistent control plane, can deploy the same definition to a self-hosted Radius installation without changing it. This compatibility is guaranteed because the initial release uses only the standard resource types that ship with Radius, so the application depends on nothing unique to Repo Radius. This is the no-lock-in portability promised in the positive user outcome. Migrating the recorded state so a self-hosted installation adopts the already-provisioned cloud resources, rather than re-provisioning them, is not planned. +Repo Radius runs the same Radius as a self-hosted installation and uses only the standard resource types that ship with Radius. Therefore an application definition is fully portable. A team that outgrows Repo Radius can deploy the same definition to a self-hosted Radius installation without modification. Limiting Repo Radius to only using the standard resource types is a limitation that is accepted to guarantee portability (this is a two-way door that can be revisited in the future). ## User Journey 5: Tailor Repo Radius for a team @@ -393,7 +397,7 @@ The requirements below are a first pass at what the initial release must deliver | FR17 | Radius data store | Shall be externalized to GitHub-native storage, loaded at the start of each run and saved at the end (resource data, application graphs, deployment history, Terraform state). | | FR18 | Radius CLI | `rad deploy` output shall be adjusted so the frontend can report resource-by-resource progress. | -**Deferred to a later release:** preview planned changes / what-if (User Story 2.2), prune on deploy (User Story 2.3), custom recipe packs (User Story 5.1), a predefined environment catalog (User Story 5.2), and state migration to self-hosted Radius (User Story 4.2). +**Deferred to a later release:** preview planned changes / what-if (User Story 2.2), custom recipe packs (User Story 5.1), a predefined environment catalog (User Story 5.2), and state migration to self-hosted Radius (User Story 4.2). ### Non-functional requirements @@ -401,7 +405,7 @@ The requirements below are a first pass at what the initial release must deliver | --- | --- | --- | | NFR1 | Performance | Control plane startup shall be fast enough that per-run latency is acceptable, since startup is on the critical path for every operation. | | NFR2 | Security | Deployments shall authenticate with short-lived, OIDC-issued cloud credentials and shall not store long-lived cloud secrets. | -| NFR3 | Security | The cloud identity shall be scoped to the least privilege required and isolated per environment to limit blast radius. | +| NFR3 | Security | The cloud identity shall be scoped to the least privilege required and isolated per repository (and per cloud account or subscription to separate production from non-production) to limit blast radius. | | NFR4 | Reliability | The data store load and save around each run shall be reliable and atomic, so a failed run does not silently corrupt or desync state. | ## Appendix 2: Dependencies and risks @@ -453,3 +457,63 @@ We considered scoping the cloud trust relationship to each individual GitHub Env * **Repository scope (recommended)**. The trust condition matches any subject from the repository (for example, `repo:my-org/my-repo:*`), so a single trust relationship and role cover every environment in the repository. Adding or renaming an environment that targets the same cloud account is purely a GitHub-side action and requires no cloud change. The trade-off is coarser isolation: all environments in the repository can assume the same cloud identity, so separation between non-production and production is achieved by targeting a different cloud account or subscription (each with its own trust and role) rather than by the trust condition alone. **Recommendation**: Repository-scoped trust. It keeps adding environments a self-service, GitHub-only action, avoids per-environment cloud configuration and Azure's federated-credential limits, and matches how teams already separate production from non-production, by using a distinct cloud account or subscription. Where stronger isolation between environments in the same account is required, a team can still create a separate role and repository-scoped trust per account. + +## Appendix 4: Example Result Artifact + +Below is an example run-rad-commands-result artifact from the prototype. This example shows the workflow being run with three commands: rad resource list -o json, rad app show todo-list -o json, and rad app graph todo-list -o json. Note that the second commands failed and the third did not execute. + +```json +{ + "schemaVersion": "1.0", + "outcome": "command_failed", + "exitCode": 1, + "environment": "production", + "commandsRequested": 3, + "commandsRan": 2, + "startedAt": "2026-06-29T18:42:01Z", + "completedAt": "2026-06-29T18:42:37Z", + "durationSeconds": 36, + "message": "1 of 3 commands failed. Remaining commands were not run.", + "commands": [ + { + "index": 0, + "command": "resource list -o json", + "artifact": "rad-command-0", + "outcome": "succeeded", + "exitCode": 0, + "startedAt": "2026-06-29T18:42:05Z", + "completedAt": "2026-06-29T18:42:11Z", + "durationSeconds": 6, + "stdout": "[{\"id\":\"/planes/radius/local/resourceGroups/default/providers/Applications.Core/containers/frontend\",\"name\":\"frontend\",\"type\":\"Applications.Core/containers\"}]", + "stderr": "", + "errorMessage": null + }, + { + "index": 1, + "command": "app show todo-list -o json", + "artifact": "rad-command-1", + "outcome": "failed", + "exitCode": 1, + "startedAt": "2026-06-29T18:42:11Z", + "completedAt": "2026-06-29T18:42:37Z", + "durationSeconds": 26, + "stdout": "", + "stderr": "Error: application \"todo-list\" not found in environment \"production\"\nTraceId: 5ec67312f45304c527560e39433eff84", + "errorMessage": "application \"todo-list\" not found in environment \"production\"" + }, + { + "index": 2, + "command": "app graph todo-list -o json", + "artifact": null, + "outcome": "skipped", + "exitCode": null, + "startedAt": null, + "completedAt": null, + "durationSeconds": null, + "stdout": null, + "stderr": null, + "errorMessage": "Not run: a prior command failed." + } + ] +} +``` \ No newline at end of file From 208ae4503da2612a84fe9d8fa156b1fa3e49311a Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Thu, 16 Jul 2026 16:30:11 -0500 Subject: [PATCH 13/17] Minor updates prior to second review Signed-off-by: Zach Casper --- eng/design-notes/github/2026-06-repo-radius.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index c49449fb8a7..660187f0fe7 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -159,6 +159,8 @@ In every case, the frontend is responsible for surfacing the outcome and its rem With the application already modeled in `app.bicep`, the developer asks the frontend to deploy it. Repo Radius runs on demand inside a GitHub Actions runner, provisions the resources defined in `app.bicep` and shuts down. The developer never installs or configures Radius or learns each provider's resource types and arguments. +This is the same standard deployment Radius performs today; Repo Radius does not change how Radius deploys applications. The only potential change to core Radius is adjusting the output of the `rad deploy` command so the frontend can collect incremental deployment progress updates. + #### User Experience The developer asks the frontend to deploy an application: @@ -197,8 +199,6 @@ On dispatch, the action checks out the repository at the requested `ref` (or the The run performs two commands: `rad deploy` provisions the resources, and `rad app graph` returns the application graph. The `rad app graph` output provides each resource's cloud provider resource ID, the connections between components, and the reachable endpoints, which the frontend surfaces so the developer can confirm everything was wired as modeled (User Experience step 4). On success, the action uploads a single `run-rad-commands-result` artifact whose `commands` array holds the output of the deployment and the application graph JSON (see Appendix 4 for an example). On a successful deployment, the action also records a GitHub Deployment for the environment at the deployed commit, so the version running in each environment is tracked in GitHub and can later be promoted to another environment (User Story 3.1). The frontend uses the streaming workflow logs to report incremental progress and the `run-rad-commands-result` artifact to report the final outcome to the developer. -This is the same standard deployment Radius performs today; Repo Radius does not change how Radius deploys applications. The only potential change to core Radius is adjusting the output of the `rad deploy` command so the frontend can collect incremental deployment progress updates. - #### Exceptions The action reports one of the following failure outcomes in the `run-rad-commands-result` artifact: @@ -348,7 +348,7 @@ A breaking change (to the workflow inputs, the allowed command set, or the resul > As a developer, I want to move my application from Repo Radius to a self-hosted Radius installation, so that I can grow beyond Repo Radius without rewriting my application. -Repo Radius runs the same Radius as a self-hosted installation and uses only the standard resource types that ship with Radius. Therefore an application definition is fully portable. A team that outgrows Repo Radius can deploy the same definition to a self-hosted Radius installation without modification. Limiting Repo Radius to only using the standard resource types is a limitation that is accepted to guarantee portability (this is a two-way door that can be revisited in the future). +Repo Radius runs the same Radius as a self-hosted installation, so an application definition is fully portable. When an application needs a resource type beyond the standard types that ship with Radius, the frontend creates the custom resource type and stores its definition in the repository's `.radius` directory alongside the application definition. Because both the application definition and any custom resource types live in the repository, nothing is locked inside Repo Radius. A team that outgrows Repo Radius migrates by taking their `app.bicep` and any custom resource types created under `.radius`, registering those resource types in their self-hosted Radius control plane, and deploying the same application definition without modification. ## User Journey 5: Tailor Repo Radius for a team @@ -360,7 +360,7 @@ User Journey 5 covers the platform engineer, not the developer. Both stories are The platform engineer wants a developer asking for a database, cache, or queue to get infrastructure that meets organizational standards for naming, tagging, network and security baselines, approved services and SKUs, and cost controls, rather than the out-of-the-box defaults. This requires replacing or extending the recipe pack Repo Radius registers on each run. -Customizing the recipe pack is out of scope for the initial release (see Non-goals). Every run uses the default AWS and Azure recipe pack that ships with Radius. In the future we imagine a platform engineer specifying an organization-wide recipe pack that Repo Radius applies in place of the default, so a developer's resource request is provisioned to the organization's standards without the developer doing anything differently. Because self-hosted Radius already supports custom recipes, this is mainly a matter of giving Repo Radius a place to reference the platform engineer's recipe pack. +Customizing the recipe pack is out of scope for the initial release (see Non-goals). Every run uses the default recipe pack that ships with Radius. In the future we imagine a platform engineer specifying an organization-wide recipe pack that Repo Radius applies in place of the default, so a developer's resource request is provisioned to the organization's standards without the developer doing anything differently. Because self-hosted Radius already supports custom recipes, this is mainly a matter of giving Repo Radius a place to reference the platform engineer's recipe pack. ### User Story 5.2: Predefine environments for developers @@ -370,6 +370,8 @@ The platform engineer wants to define the environments teams are allowed to depl Because Repo Radius models each environment as a GitHub Environment (User Story 1.1), a platform engineer can already create and govern these environments using GitHub's native environment management and protection rules, and developers deploy to the ones that exist. A first-class experience for predefining a catalog of approved environments that developers select from, with organization-wide governance, is deferred and out of scope for this specification. +We considered a Radius-specific solution for platform engineers to define multiple environments via an `env.bicep` file in the repository instead of modeling each environment as a GitHub Environment. The drawback is that this approach duplicates a concept GitHub already provides. Cloud credentials must still be stored as GitHub Environment secrets, so an `env.bicep` would split related configuration across two locations: non-secret details in the file and secrets in the GitHub Environment. See Appendix 3: Alternatives considered for a deeper discussion. + ## Appendix 1: Requirements The requirements below are a first pass at what the initial release must deliver, grouped by component. This is a starting point for planning, not a final list. From f95fcb9c7c3516a7abd80a9a1698c8326474bdee Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 21 Jul 2026 16:59:38 -0500 Subject: [PATCH 14/17] Updated migration user story Signed-off-by: Zach Casper --- eng/design-notes/github/2026-06-repo-radius.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 660187f0fe7..5a95383d413 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -25,6 +25,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. It transform * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Supporting multi-repo applications is deferred to a later date. +* Defining the migration process for moving an application from Repo Radius to self-hosted Radius. Repo Radius does not inhibit migration (the application definition is standard, portable Radius), but the migration experience depends on the frontend and the solution Repo Radius is embedded in and requires its own feature specification (User Story 4.2). * Customizing, hardening, or expanding the recipe pack. The initial release uses the default AWS and Azure recipe pack that ships with Radius as-is. ## Definition of terms @@ -78,7 +79,7 @@ I am a platform engineer supporting several teams. I want my developers to deplo ### Positive user outcome -A developer can deploy to AWS or Azure without installing Kubernetes or self-hosted Radius. They create environments with their cloud account details and ask for their application to be deployed and Repo Radius provisions the exact infrastructure needed for the application in a secure and cost-efficient manner. As they modify their application, Repo Radius updates the deployed infrastructure to match the updated application. If they outgrow Repo Radius, the developer can migrate to self-hosted Radius without changing their application definition. +A developer can deploy to AWS or Azure without installing Kubernetes or self-hosted Radius. They create environments with their cloud account details and ask for their application to be deployed and Repo Radius provisions the exact infrastructure needed for the application in a secure and cost-efficient manner. As they modify their application, Repo Radius updates the deployed infrastructure to match the updated application. Because Repo Radius runs standard Radius and the application definition lives in the developer's own repository, they are not locked in if they outgrow Repo Radius, though the migration process itself is out of scope for this specification. A platform engineer can define organization-wide recipes once and every team inherits them, and is able to programmatically create environments with cloud credentials so developers keep a fast, self-service experience while the platform engineer keeps the consistency, security, and governance they are accountable for. @@ -346,9 +347,11 @@ A breaking change (to the workflow inputs, the allowed command set, or the resul ### User Story 4.2: Migrate to self-hosted Radius -> As a developer, I want to move my application from Repo Radius to a self-hosted Radius installation, so that I can grow beyond Repo Radius without rewriting my application. +> As a developer, I want to grow beyond Repo Radius to a self-hosted Radius installation without being locked in, so that outgrowing Repo Radius is not a dead end. -Repo Radius runs the same Radius as a self-hosted installation, so an application definition is fully portable. When an application needs a resource type beyond the standard types that ship with Radius, the frontend creates the custom resource type and stores its definition in the repository's `.radius` directory alongside the application definition. Because both the application definition and any custom resource types live in the repository, nothing is locked inside Repo Radius. A team that outgrows Repo Radius migrates by taking their `app.bicep` and any custom resource types created under `.radius`, registering those resource types in their self-hosted Radius control plane, and deploying the same application definition without modification. +Repo Radius runs the same Radius as a self-hosted installation, so it does nothing to inhibit migration: the application definition is standard Radius and fully portable, and the application definition (plus any custom resource types the frontend stores in the repository) lives in the user's own GitHub repository rather than inside Repo Radius. There is no proprietary format or hidden state that ties an application to Repo Radius. + +The migration *process*, however, is out of scope for this specification. A real migration has to account for more than the portable application definition: the externalized data store (see FR17), the already-provisioned cloud resources that a self-hosted control plane must adopt, and where the frontend chooses to keep application and custom-resource-type definitions. These depend heavily on the frontend and the solution Repo Radius is embedded in, so a dedicated feature specification is required to define the migration experience. It is called out here so the evolve journey reflects where migration belongs, and left undetailed pending that spec. ## User Journey 5: Tailor Repo Radius for a team @@ -399,7 +402,7 @@ The requirements below are a first pass at what the initial release must deliver | FR17 | Radius data store | Shall be externalized to GitHub-native storage, loaded at the start of each run and saved at the end (resource data, application graphs, deployment history, Terraform state). | | FR18 | Radius CLI | `rad deploy` output shall be adjusted so the frontend can report resource-by-resource progress. | -**Deferred to a later release:** preview planned changes / what-if (User Story 2.2), custom recipe packs (User Story 5.1), a predefined environment catalog (User Story 5.2), and state migration to self-hosted Radius (User Story 4.2). +**Deferred to a later release:** preview planned changes / what-if (User Story 2.2), custom recipe packs (User Story 5.1), a predefined environment catalog (User Story 5.2), and a migration experience for moving to self-hosted Radius (User Story 4.2). ### Non-functional requirements From 60625033d88a7f2deada191f0e008e3bd6358b16 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 21 Jul 2026 17:27:04 -0500 Subject: [PATCH 15/17] Updated upgrade story Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 5a95383d413..b9ff0b7866f 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -119,7 +119,7 @@ Once these steps are complete, the frontend asks Repo Radius to verify the conne Completing these steps produces the following, all stored in the developer's own GitHub account: -* **Two committed workflows** in `.github/workflows/`. Each is a thin wrapper that invokes a published Repo Radius GitHub Action pinned to a major version tag (for example, `@v1`), so a committed workflow only changes when the repository adopts a new major version. +* **Two committed workflows** in `.github/workflows`. Each is a thin wrapper that invokes a published Repo Radius GitHub Action pinned to an exact commit SHA, so a committed workflow runs one specific, verified version of the action until it is updated (User Story 4.1). | Workflow | Action | Purpose | | --- | --- | --- | @@ -339,11 +339,31 @@ The developer asks the frontend to delete an application from an environment. Th ### User Story 4.1: Upgrade Repo Radius -> As a developer, I want to keep Repo Radius up to date, so that I get fixes and improvements without performing manual upgrades. +> As a developer, I want to keep Repo Radius up to date, so that I get fixes and improvements without managing versions by hand. -Repo Radius ships as the two GitHub Actions (User Story 1.1), published to the GitHub Marketplace and versioned by major tag. The committed workflows reference each action by its major tag (for example, `radius-project/run-rad-commands@v1`), so every run automatically picks up the latest backward-compatible release of that major version without the developer changing anything. +#### Summary + +Two independently versioned components are involved in an upgrade. Repo Radius itself is delivered as the two GitHub Actions (User Story 1.1) published to the GitHub Marketplace. The frontend that drives Repo Radius is a separate component, part of the solution Repo Radius is embedded in, installed and updated on its own. The workflows committed to the repository pin each Repo Radius action to an exact commit SHA, so a repository always runs one specific, verified version of each action rather than whatever a moving tag points at. Updating the frontend does not change those pinned workflows, so the frontend and the pinned actions can drift apart. Keeping them in sync is the frontend's responsibility, not Repo Radius's: because the frontend committed the workflows, it is the component that must update them. At deploy time, before dispatching the deployment workflow, the frontend checks the pinned action versions against the versions it requires, and when they are behind it updates the workflows with the developer's confirmation and through a path that respects the repository's branch protections. Repo Radius does nothing here; it only runs whatever version the workflow pins. + +#### User Experience + +Keeping Repo Radius up to date involves two things, only one of which the developer initiates directly: + +1. **Update the frontend.** The developer updates the frontend through its own update mechanism, which downloads the latest frontend to the local machine. This updates only the local frontend; it does not touch the workflows committed to any repository. + +2. **Confirm a workflow update at deploy time.** When the developer next asks to deploy (User Story 1.2), the frontend compares the action versions pinned in the repository's workflows against the versions the current frontend requires. If the workflows already satisfy the requirement, the deployment proceeds unchanged and the developer sees nothing. If the frontend requires a newer version of the actions, the frontend tells the developer the workflows must be updated and asks for confirmation before changing anything in the repository. On confirmation, the frontend updates the workflows by committing to the default branch, or by opening a pull request if the branch is protected (see Result), and once the update is in place the deployment proceeds with the up-to-date actions. If the workflows cannot be updated, the deployment does not proceed. + +#### Result + +When the developer creates an environment (User Story 1.1), the frontend writes the Repo Radius workflows to the repository's `.github/workflows` directory, with each `uses` statement pinned to an exact commit SHA. Pinning by SHA makes every run reproducible and prevents an upstream release from silently changing behavior. + +At deploy time, when the frontend determines the pinned actions are older than it requires and the developer confirms the update, it writes the updated SHAs into the workflows through whichever path the repository allows: + +* It attempts to commit the updated workflows directly to the default branch. +* If the default branch is protected, it offers to open a pull request with the updated workflows instead. +* If the developer lacks permission to open that pull request, the workflows cannot be updated and the deployment fails, because the required action version is not present in the repository. -A breaking change (to the workflow inputs, the allowed command set, or the result artifact schema) ships as a new major version (`@v2`). The existing `@v1` continues to work, so repositories are unaffected until they opt in. Adopting a new major version requires the user, or the frontend to update the tag in the workflow stored in the repository. +Once the updated workflows are present on the default branch (and merged into the branch being deployed, if a different `ref` is targeted), the deployment proceeds using the up-to-date actions. Because the version that runs is always the commit SHA committed to the repository, every upgrade is explicit and auditable, arriving through either a direct commit or a reviewed pull request. ### User Story 4.2: Migrate to self-hosted Radius @@ -383,11 +403,11 @@ The requirements below are a first pass at what the initial release must deliver | ID | Component | Functional requirement | | --- | --- | --- | -| FR1 | `verify-cloud-auth` action | Shall be published to the Marketplace, versioned by major tag. | +| FR1 | `verify-cloud-auth` action | Shall be published to the Marketplace and released under immutable version tags that resolve to specific commit SHAs. | | FR2 | `verify-cloud-auth` action | Shall request a GitHub OIDC token, exchange it with AWS or Azure, confirm the short-lived credentials are valid, and verify the identity's permissions. | | FR3 | `verify-cloud-auth` action | Shall confirm the workload cluster's API server is reachable when a cluster name is present. | | FR4 | `verify-cloud-auth` action | Shall publish a `verify-cloud-auth-result` artifact with a versioned JSON schema (outcome, message, remediation) under a stable artifact name. | -| FR5 | `run-rad-commands` action | Shall be published to the Marketplace, versioned by major tag. | +| FR5 | `run-rad-commands` action | Shall be published to the Marketplace and released under immutable version tags that resolve to specific commit SHAs. | | FR6 | `run-rad-commands` action | Shall check out the repository at the requested `ref` commit or tag before running commands. | | FR7 | `run-rad-commands` action | Shall validate each entry in `rad_commands` against an allowed-command enum before executing anything. | | FR8 | `run-rad-commands` action | Shall authenticate to AWS or Azure using the environment's OIDC variables. | @@ -398,7 +418,7 @@ The requirements below are a first pass at what the initial release must deliver | FR13 | `run-rad-commands` action | Shall record a GitHub Deployment on a successful deploy and mark it inactive on delete. | | FR14 | `run-rad-commands` action | Shall publish a `run-rad-commands-result` artifact with a versioned JSON schema and an ordered `commands` array under a stable artifact name. | | FR15 | `run-rad-commands` action | Shall report a failed data-store save distinctly (the "State could not be saved" outcome) so the developer knows the cloud may hold resources the persisted state does not reflect. | -| FR16 | Slim workflows | Shall be provided as ready-to-commit `radius-verify-cloud-auth.yml` and `radius-run-rad-commands.yml` that invoke the actions at their major tag. | +| FR16 | Slim workflows | Shall be provided as ready-to-commit `radius-verify-cloud-auth.yml` and `radius-run-rad-commands.yml`, written to `.github/workflows`, that pin each action to an exact commit SHA. | | FR17 | Radius data store | Shall be externalized to GitHub-native storage, loaded at the start of each run and saved at the end (resource data, application graphs, deployment history, Terraform state). | | FR18 | Radius CLI | `rad deploy` output shall be adjusted so the frontend can report resource-by-resource progress. | From aa1d61394252357b1b611820a4f588ab658fca8d Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 21 Jul 2026 17:29:02 -0500 Subject: [PATCH 16/17] Spelling Signed-off-by: Zach Casper --- eng/design-notes/github/2026-06-repo-radius.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index b9ff0b7866f..1bbbdd49e5a 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -253,7 +253,7 @@ If the named application has not been deployed to the environment, the `rad app Before redeploying (User Story 2.3), the developer wants to see what a deployment will change: which resources it will add, change, or remove. This is analogous to `terraform plan` or `az deployment group what-if`. Producing a trustworthy plan means comparing the application's desired state (the edited `app.bicep`) against its actual deployed state and predicting how the deployment engine and recipes would resolve the difference between the two, including provider-level effects such as whether a change forces a resource to be replaced. The frontend cannot do this on its own; only Radius, which owns the deployment engine and recipe resolution, can produce an accurate what-if. -This preview-before-apply capability is central to deploying with confidence, and it is a significant new feature that is not specific to Repo Radius—self-hosted Radius needs the same what-if, and both versions of Radius would use it. Because it is a generalized feature, a standalone feature specification is required that defines the user experience and functionality. It is called out here so the iterate journey reflects where it belongs, and left undetailed pending that dedicated spec. +This preview-before-apply capability is central to deploying with confidence, and it is a significant new feature that is not specific to Repo Radius—self-hosted Radius needs the same what-if, and both versions of Radius would use it. Because it is a generalized feature, a standalone feature specification is required that defines the user experience and functionality. It is called out here so the iterate journey reflects where it belongs, and left unspecified pending that dedicated spec. ### User Story 2.3: Change my application and redeploy @@ -431,7 +431,7 @@ The requirements below are a first pass at what the initial release must deliver | NFR1 | Performance | Control plane startup shall be fast enough that per-run latency is acceptable, since startup is on the critical path for every operation. | | NFR2 | Security | Deployments shall authenticate with short-lived, OIDC-issued cloud credentials and shall not store long-lived cloud secrets. | | NFR3 | Security | The cloud identity shall be scoped to the least privilege required and isolated per repository (and per cloud account or subscription to separate production from non-production) to limit blast radius. | -| NFR4 | Reliability | The data store load and save around each run shall be reliable and atomic, so a failed run does not silently corrupt or desync state. | +| NFR4 | Reliability | The data store load and save around each run shall be reliable and atomic, so a failed run does not silently corrupt or make the state not in sync. | ## Appendix 2: Dependencies and risks From df19e5fa90444ab822f7433461c9e8d042dd4991 Mon Sep 17 00:00:00 2001 From: Zach Casper Date: Tue, 21 Jul 2026 17:49:33 -0500 Subject: [PATCH 17/17] Simplification of verbiage Signed-off-by: Zach Casper --- .../github/2026-06-repo-radius.md | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/eng/design-notes/github/2026-06-repo-radius.md b/eng/design-notes/github/2026-06-repo-radius.md index 1bbbdd49e5a..55218ffd8aa 100644 --- a/eng/design-notes/github/2026-06-repo-radius.md +++ b/eng/design-notes/github/2026-06-repo-radius.md @@ -25,7 +25,7 @@ Repo Radius is a rethinking of how to deliver Radius to developers. It transform * Supporting non-GitHub source control platforms (GitLab, Bitbucket, Azure DevOps). * Running Repo Radius outside of GitHub Actions (e.g., locally on a developer's workstation as the primary mode). * Supporting multi-repo applications is deferred to a later date. -* Defining the migration process for moving an application from Repo Radius to self-hosted Radius. Repo Radius does not inhibit migration (the application definition is standard, portable Radius), but the migration experience depends on the frontend and the solution Repo Radius is embedded in and requires its own feature specification (User Story 4.2). +* Defining the migration process for moving an application from Repo Radius to self-hosted Radius. Repo Radius does not inhibit migration, but the migration experience requires its own feature specification. * Customizing, hardening, or expanding the recipe pack. The initial release uses the default AWS and Azure recipe pack that ships with Radius as-is. ## Definition of terms @@ -343,35 +343,27 @@ The developer asks the frontend to delete an application from an environment. Th #### Summary -Two independently versioned components are involved in an upgrade. Repo Radius itself is delivered as the two GitHub Actions (User Story 1.1) published to the GitHub Marketplace. The frontend that drives Repo Radius is a separate component, part of the solution Repo Radius is embedded in, installed and updated on its own. The workflows committed to the repository pin each Repo Radius action to an exact commit SHA, so a repository always runs one specific, verified version of each action rather than whatever a moving tag points at. Updating the frontend does not change those pinned workflows, so the frontend and the pinned actions can drift apart. Keeping them in sync is the frontend's responsibility, not Repo Radius's: because the frontend committed the workflows, it is the component that must update them. At deploy time, before dispatching the deployment workflow, the frontend checks the pinned action versions against the versions it requires, and when they are behind it updates the workflows with the developer's confirmation and through a path that respects the repository's branch protections. Repo Radius does nothing here; it only runs whatever version the workflow pins. +An upgrade involves two independently versioned components: Repo Radius, delivered as the two GitHub Actions (User Story 1.1) published to the Marketplace, and the frontend that drives it, installed and updated on its own. The committed workflows pin each action to an exact commit SHA, so a repository runs one specific, verified version until the pin changes. Updating the frontend does not touch those pinned workflows, so keeping them in sync is the frontend's job: because it committed the workflows, it is the component that updates them. Repo Radius only runs whatever version the workflow pins. #### User Experience Keeping Repo Radius up to date involves two things, only one of which the developer initiates directly: -1. **Update the frontend.** The developer updates the frontend through its own update mechanism, which downloads the latest frontend to the local machine. This updates only the local frontend; it does not touch the workflows committed to any repository. +1. **Update the frontend.** The developer updates the frontend through its own update mechanism. This does not touch the workflows committed to any repository. -2. **Confirm a workflow update at deploy time.** When the developer next asks to deploy (User Story 1.2), the frontend compares the action versions pinned in the repository's workflows against the versions the current frontend requires. If the workflows already satisfy the requirement, the deployment proceeds unchanged and the developer sees nothing. If the frontend requires a newer version of the actions, the frontend tells the developer the workflows must be updated and asks for confirmation before changing anything in the repository. On confirmation, the frontend updates the workflows by committing to the default branch, or by opening a pull request if the branch is protected (see Result), and once the update is in place the deployment proceeds with the up-to-date actions. If the workflows cannot be updated, the deployment does not proceed. +2. **Confirm a workflow update at deploy time.** The next time the developer deploys (User Story 1.2), the frontend compares the pinned action versions against the versions it requires. If they already match, the deployment proceeds and the developer sees nothing. If a newer version is required, the frontend asks for confirmation and updates the workflows before deploying. If the workflows cannot be updated, the deployment does not proceed. #### Result -When the developer creates an environment (User Story 1.1), the frontend writes the Repo Radius workflows to the repository's `.github/workflows` directory, with each `uses` statement pinned to an exact commit SHA. Pinning by SHA makes every run reproducible and prevents an upstream release from silently changing behavior. - -At deploy time, when the frontend determines the pinned actions are older than it requires and the developer confirms the update, it writes the updated SHAs into the workflows through whichever path the repository allows: - -* It attempts to commit the updated workflows directly to the default branch. -* If the default branch is protected, it offers to open a pull request with the updated workflows instead. -* If the developer lacks permission to open that pull request, the workflows cannot be updated and the deployment fails, because the required action version is not present in the repository. - -Once the updated workflows are present on the default branch (and merged into the branch being deployed, if a different `ref` is targeted), the deployment proceeds using the up-to-date actions. Because the version that runs is always the commit SHA committed to the repository, every upgrade is explicit and auditable, arriving through either a direct commit or a reviewed pull request. +The frontend writes the workflows to `.github/workflows` with each `uses` statement pinned to an exact commit SHA, so every run is reproducible. When an update is needed and confirmed, it writes the new SHAs through whichever path the repository allows: a direct commit to the default branch, or a pull request if the branch is protected. If the developer cannot open that pull request, the workflows cannot be updated and the deployment fails. Because the version that runs is always the SHA committed to the repository, every upgrade is explicit and auditable. ### User Story 4.2: Migrate to self-hosted Radius -> As a developer, I want to grow beyond Repo Radius to a self-hosted Radius installation without being locked in, so that outgrowing Repo Radius is not a dead end. +> As a developer, I want to move my application from Repo Radius to a self-hosted Radius installation, so that I can grow beyond Repo Radius without rewriting my application. Repo Radius runs the same Radius as a self-hosted installation, so it does nothing to inhibit migration: the application definition is standard Radius and fully portable, and the application definition (plus any custom resource types the frontend stores in the repository) lives in the user's own GitHub repository rather than inside Repo Radius. There is no proprietary format or hidden state that ties an application to Repo Radius. -The migration *process*, however, is out of scope for this specification. A real migration has to account for more than the portable application definition: the externalized data store (see FR17), the already-provisioned cloud resources that a self-hosted control plane must adopt, and where the frontend chooses to keep application and custom-resource-type definitions. These depend heavily on the frontend and the solution Repo Radius is embedded in, so a dedicated feature specification is required to define the migration experience. It is called out here so the evolve journey reflects where migration belongs, and left undetailed pending that spec. +The migration *process*, however, is out of scope for this specification. A real migration has to account for more than the portable application definition: the externalized data store (see FR17), the already-provisioned cloud resources that a self-hosted control plane must adopt, and where the frontend chooses to keep application and custom-resource-type definitions. These depend heavily on the frontend and the solution Repo Radius is embedded in, so a dedicated feature specification is required to define the migration experience. It is called out here so the evolve journey reflects where migration belongs, and left unspecified pending that spec. ## User Journey 5: Tailor Repo Radius for a team