From 1752ee3651d955c29c6cf882ddf36e895fea5a32 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Feb 2026 20:52:16 +0000
Subject: [PATCH 1/3] Initial plan
From 55a485104146349fb18e8f580344c08c1642d7a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Feb 2026 21:02:09 +0000
Subject: [PATCH 2/3] Add CI/CD deployment guide and update sidebar
Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
---
.../config/sidebar/deployment.topics.ts | 22 ++
.../src/content/docs/deployment/cicd.mdx | 348 ++++++++++++++++++
2 files changed, 370 insertions(+)
create mode 100644 src/frontend/src/content/docs/deployment/cicd.mdx
diff --git a/src/frontend/config/sidebar/deployment.topics.ts b/src/frontend/config/sidebar/deployment.topics.ts
index fd9fb5d98..a91f352b5 100644
--- a/src/frontend/config/sidebar/deployment.topics.ts
+++ b/src/frontend/config/sidebar/deployment.topics.ts
@@ -110,6 +110,28 @@ export const deploymentTopics: StarlightSidebarTopicsUserConfig = {
},
slug: 'deployment/custom-deployments',
},
+ {
+ label: 'CI/CD pipelines',
+ translations: {
+ da: 'CI/CD-pipelines',
+ de: 'CI/CD-Pipelines',
+ en: 'CI/CD pipelines',
+ es: 'Canalizaciones de CI/CD',
+ fr: 'Pipelines CI/CD',
+ hi: 'CI/CD पाइपलाइन',
+ id: 'Pipeline CI/CD',
+ it: 'Pipeline CI/CD',
+ ja: 'CI/CD パイプライン',
+ ko: 'CI/CD 파이프라인',
+ 'pt-BR': 'Pipelines de CI/CD',
+ 'pt-PT': 'Pipelines de CI/CD',
+ ru: 'Конвейеры CI/CD',
+ tr: 'CI/CD işlem hatları',
+ uk: 'Конвеєри CI/CD',
+ 'zh-CN': 'CI/CD 管道',
+ },
+ slug: 'deployment/cicd',
+ },
{
label: 'Deploy to Azure',
collapsed: false,
diff --git a/src/frontend/src/content/docs/deployment/cicd.mdx b/src/frontend/src/content/docs/deployment/cicd.mdx
new file mode 100644
index 000000000..de2f4cee6
--- /dev/null
+++ b/src/frontend/src/content/docs/deployment/cicd.mdx
@@ -0,0 +1,348 @@
+---
+title: Deploy Aspire apps in CI/CD pipelines
+description: Learn how to deploy Aspire applications from CI/CD pipelines using GitHub Actions and Azure DevOps.
+---
+
+import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
+import LearnMore from '@components/LearnMore.astro';
+
+Deploying Aspire applications from continuous integration and continuous delivery (CI/CD) pipelines requires a few extra considerations compared to interactive local deployments. This guide covers how to configure the Aspire CLI for non-interactive use, set up GitHub Actions and Azure DevOps pipelines, and handle Azure authentication from service principals.
+
+## Non-interactive deployment
+
+The `aspire deploy` command is **interactive by default**—it prompts you to select an Azure tenant, subscription, and resource group. In a CI/CD environment there is no terminal operator, so you must supply those values through environment variables to suppress the prompts:
+
+| Environment variable | Description |
+|---|---|
+| `Azure__SubscriptionId` | The Azure subscription ID to deploy into. |
+| `Azure__Location` | The Azure region (for example, `eastus`). |
+| `Azure__ResourceGroup` | The resource group name to create or reuse. |
+
+When all three variables are set, `aspire deploy` skips the interactive tenant/subscription/resource-group prompts and proceeds automatically.
+
+
+
+## Azure authentication in CI/CD
+
+### Service principal vs. interactive user login
+
+Locally you typically run `az login` to authenticate as a **user principal**. In CI/CD pipelines the agent runs as a **service principal** (via a federated credential or a client secret), which causes a small but important difference for some Azure resources.
+
+For example, Cosmos DB uses `principalType: "User"` for interactive logins but `principalType: "ServicePrincipal"` for headless service-principal logins. If you provision Cosmos DB through Aspire with an interactive login and later deploy from a pipeline with a service principal, the role assignment may fail.
+
+To avoid this, use **OpenID Connect (OIDC) / Workload Identity Federation** when possible. This avoids storing long-lived secrets and uses a short-lived token that is automatically associated with the correct principal type.
+
+### Logging in with a service principal
+
+
+
+
+Use the [`azure/login`](https://github.com/Azure/login) action with OIDC:
+
+```yaml title="GitHub Actions — Azure login with OIDC"
+- name: Azure login
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZURE_CLIENT_ID }}
+ tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+ subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+```
+
+Or with a client secret (less preferred):
+
+```yaml title="GitHub Actions — Azure login with client secret"
+- name: Azure login
+ uses: azure/login@v2
+ with:
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
+```
+
+
+
+
+Use the [Azure CLI task](https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/azure-cli-v2) with a service connection:
+
+```yaml title="Azure DevOps — Azure CLI task"
+- task: AzureCLI@2
+ displayName: Deploy with Aspire CLI
+ inputs:
+ azureSubscription: ''
+ scriptType: bash
+ scriptLocation: inlineScript
+ inlineScript: |
+ aspire deploy \
+ --project src/AppHost/AppHost.csproj
+ env:
+ Azure__SubscriptionId: $(AZURE_SUBSCRIPTION_ID)
+ Azure__Location: $(AZURE_LOCATION)
+ Azure__ResourceGroup: $(AZURE_RESOURCE_GROUP)
+```
+
+When the `AzureCLI@2` task runs, it automatically authenticates the Azure CLI using the service connection—no separate `az login` step is required.
+
+
+
+
+## GitHub Actions workflow
+
+The following workflow builds and deploys an Aspire application to Azure Container Apps. It triggers on pushes to the `main` branch and uses OIDC for passwordless Azure authentication.
+
+```yaml title="GitHub Actions — .github/workflows/deploy.yml"
+name: Deploy to Azure Container Apps
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+permissions:
+ id-token: write # Required for OIDC token exchange
+ contents: read
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
+ - name: Install Aspire CLI
+ run: dotnet tool install -g aspire.cli
+
+ - name: Azure login
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZURE_CLIENT_ID }}
+ tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+ subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ - name: Deploy
+ run: aspire deploy --project src/AppHost/AppHost.csproj
+ env:
+ Azure__SubscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ Azure__Location: ${{ vars.AZURE_LOCATION }}
+ Azure__ResourceGroup: ${{ vars.AZURE_RESOURCE_GROUP }}
+```
+
+### Setting up OIDC for GitHub Actions
+
+
+
+1. In the Azure portal, create an **App Registration** (or use an existing one).
+
+1. Add a **Federated credential** to the App Registration:
+
+ - **Federated credential scenario**: GitHub Actions deploying Azure resources
+ - **Organization**: your GitHub org or username
+ - **Repository**: your repository name
+ - **Entity type**: Branch
+ - **Branch**: `main` (or the branch you deploy from)
+
+1. Assign the App Registration a role on the target subscription or resource group:
+
+ ```azurecli title="Azure CLI — Role assignment"
+ az role assignment create \
+ --assignee \
+ --role Contributor \
+ --scope /subscriptions/
+ ```
+
+1. Add the following secrets to your GitHub repository (**Settings > Secrets and variables > Actions**):
+
+ | Secret name | Value |
+ |---|---|
+ | `AZURE_CLIENT_ID` | The App Registration's Application (client) ID |
+ | `AZURE_TENANT_ID` | Your Azure tenant ID |
+ | `AZURE_SUBSCRIPTION_ID` | Your Azure subscription ID |
+
+1. Add the following variables (non-sensitive):
+
+ | Variable name | Example value |
+ |---|---|
+ | `AZURE_LOCATION` | `eastus` |
+ | `AZURE_RESOURCE_GROUP` | `my-aspire-app-rg` |
+
+
+
+
+For more information, see [GitHub's OIDC documentation](https://docs.github.com/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect).
+
+
+## Azure DevOps pipeline
+
+The following pipeline deploys an Aspire application to Azure Container Apps using an Azure DevOps service connection.
+
+```yaml title="Azure DevOps — azure-pipelines.yml"
+trigger:
+ branches:
+ include:
+ - main
+
+pool:
+ vmImage: ubuntu-latest
+
+variables:
+ - group: aspire-deploy-vars # Variable group with AZURE_* variables
+
+steps:
+ - task: UseDotNet@2
+ displayName: Set up .NET
+ inputs:
+ packageType: sdk
+ version: '9.x'
+
+ - script: dotnet tool install -g aspire.cli
+ displayName: Install Aspire CLI
+
+ - task: AzureCLI@2
+ displayName: Deploy with Aspire CLI
+ inputs:
+ azureSubscription: ''
+ scriptType: bash
+ scriptLocation: inlineScript
+ inlineScript: aspire deploy --project src/AppHost/AppHost.csproj
+ env:
+ Azure__SubscriptionId: $(AZURE_SUBSCRIPTION_ID)
+ Azure__Location: $(AZURE_LOCATION)
+ Azure__ResourceGroup: $(AZURE_RESOURCE_GROUP)
+```
+
+### Setting up the service connection
+
+
+
+1. In Azure DevOps, navigate to **Project settings > Service connections**.
+
+1. Select **New service connection** and choose **Azure Resource Manager**.
+
+1. Choose **Workload Identity federation (automatic)** for the authentication method—this creates a federated credential in Azure automatically.
+
+1. Select your subscription and (optionally) a resource group scope.
+
+1. Give the connection a name and save it. Use this name in the `azureSubscription` field of your pipeline task.
+
+
+
+### Using a variable group
+
+
+
+1. In Azure DevOps, navigate to **Pipelines > Library** and create a **Variable group** named `aspire-deploy-vars`.
+
+1. Add the following variables:
+
+ | Variable | Example value | Secret |
+ |---|---|---|
+ | `AZURE_SUBSCRIPTION_ID` | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | Yes |
+ | `AZURE_LOCATION` | `eastus` | No |
+ | `AZURE_RESOURCE_GROUP` | `my-aspire-app-rg` | No |
+
+1. Link the variable group in your pipeline using the `variables` block shown above.
+
+
+
+## CI environment tips
+
+### Terminal output and formatting
+
+The Aspire CLI detects whether it's running in a CI environment and adjusts its output accordingly (no interactive prompts, plain-text progress). If you see garbled or ANSI escape codes in logs, set the `NO_COLOR` environment variable:
+
+```yaml title="Disable color output"
+env:
+ NO_COLOR: '1'
+```
+
+### Timeouts
+
+Deployments to Azure Container Apps can take several minutes. Make sure your pipeline job timeout is set high enough (at least 15–20 minutes) to allow for:
+
+- .NET container image builds
+- Azure Container Registry provisioning and image push
+- Container Apps environment and app provisioning
+
+
+
+
+```yaml title="GitHub Actions — job timeout"
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+```
+
+
+
+
+```yaml title="Azure DevOps — step timeout"
+- task: AzureCLI@2
+ displayName: Deploy with Aspire CLI
+ timeoutInMinutes: 30
+ inputs:
+ # ...
+```
+
+
+
+
+### Docker availability
+
+The Aspire CLI builds container images during deployment. Ensure Docker is available on the build agent:
+
+- **GitHub Actions**: Docker is pre-installed on `ubuntu-latest` and `windows-latest` runners.
+- **Azure DevOps**: Docker is pre-installed on Microsoft-hosted `ubuntu-latest` agents. Self-hosted agents may need Docker installed separately.
+
+
+
+### Caching deployment state
+
+The Aspire CLI caches deployment state (provisioned resource IDs, resolved parameter values) to speed up subsequent runs. In CI/CD you typically want one of two behaviors:
+
+- **Ephemeral (fresh deploy every run)**: Use `--clear-cache` to discard saved state and provision from scratch.
+- **Incremental (update existing resources)**: Persist the `.aspire/` directory between runs using your pipeline's cache mechanism and set `Azure__ResourceGroup` to the same group on every run.
+
+
+
+
+```yaml title="GitHub Actions — cache .aspire directory"
+- name: Restore deployment cache
+ uses: actions/cache@v4
+ with:
+ path: .aspire
+ key: aspire-deploy-${{ github.ref_name }}
+```
+
+
+
+
+```yaml title="Azure DevOps — cache .aspire directory"
+- task: Cache@2
+ inputs:
+ key: aspire-deploy | $(Build.SourceBranchName)
+ path: .aspire
+ displayName: Restore deployment cache
+```
+
+
+
+
+
+For more information about deployment state caching, see [Deployment state caching](/deployment/deployment-state-caching/).
+
+
+## See also
+
+- [Deploy using the Aspire CLI](/deployment/azure/aca-deployment-aspire-cli/)
+- [Azure security best practices](/deployment/azure/azure-security-best-practices/)
+- [Deployment state caching](/deployment/deployment-state-caching/)
+- [Publishing and deployment overview](/deployment/overview/)
+- [`aspire deploy` command reference](/reference/cli/commands/aspire-deploy/)
From b3eb86066fec0cd1af7ec1e8bb381fbbe41969a2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 00:10:08 +0000
Subject: [PATCH 3/3] Update .NET version to 10.x in CI/CD examples
Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
---
src/frontend/src/content/docs/deployment/cicd.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/frontend/src/content/docs/deployment/cicd.mdx b/src/frontend/src/content/docs/deployment/cicd.mdx
index de2f4cee6..f6febd99a 100644
--- a/src/frontend/src/content/docs/deployment/cicd.mdx
+++ b/src/frontend/src/content/docs/deployment/cicd.mdx
@@ -112,7 +112,7 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '9.x'
+ dotnet-version: '10.x'
- name: Install Aspire CLI
run: dotnet tool install -g aspire.cli
@@ -197,7 +197,7 @@ steps:
displayName: Set up .NET
inputs:
packageType: sdk
- version: '9.x'
+ version: '10.x'
- script: dotnet tool install -g aspire.cli
displayName: Install Aspire CLI