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..7fc552390 --- /dev/null +++ b/src/frontend/src/content/docs/deployment/cicd.mdx @@ -0,0 +1,331 @@ +--- +title: Deploy Aspire apps in CI/CD pipelines +description: Learn how to use aspire publish with GitHub Actions and Azure DevOps to build container images and generate deployment artifacts for any target platform. +--- + +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 follows a consistent pattern regardless of your target platform: generate deployment artifacts, push container images to a registry, then apply the artifacts using your preferred tooling. + +## CI/CD workflow overview + +The recommended CI/CD pattern for Aspire applications uses `aspire publish` to produce deployment artifacts and then pushes the built images to a container registry. The artifacts (Docker Compose files, Kubernetes manifests, or other formats) reference your images by a placeholder that you resolve at deploy time. + +```mermaid +architecture-beta + service ci(logos:github-actions)[CI runner] + service reg(logos:docker)[Container registry] + service target(logos:kubernetes)[Deployment target] + + ci:R --> L:reg + reg:R --> L:target +``` + +The workflow breaks down into three phases: + + + +1. **Build & publish** — `aspire publish` builds your .NET project container images and generates deployment artifacts (Docker Compose files, Kubernetes manifests, etc.) with image name placeholders. + +1. **Push** — Your pipeline tags the locally-built images and pushes them to a container registry such as GitHub Container Registry, Docker Hub, Amazon ECR, or Azure Container Registry. + +1. **Deploy** — Your pipeline applies the generated artifacts using the appropriate tooling (`kubectl apply`, `docker compose up`, etc.), substituting the image placeholders with the pushed registry addresses. + + + + + + + Learn more: [Publishing and deployment overview](/deployment/overview/) + + +## Running aspire publish in CI + +The `aspire publish` command is **non-interactive by default** — it does not prompt for input and writes all artifacts to the output path you specify. This makes it straightforward to use in automated pipelines. + +```bash title="Aspire CLI — Generate deployment artifacts" +aspire publish --project src/AppHost/AppHost.csproj -o ./artifacts +``` + +The output directory contains the deployment manifests and the container images are built into the local Docker daemon during the publish step. You push those images to a registry as a separate step. + + + +## Pushing container images to a registry + +After `aspire publish` builds your images locally, tag and push them to your registry using standard Docker CLI commands. + +### GitHub Container Registry + +[GitHub Container Registry (GHCR)](https://docs.github.com/packages/working-with-a-github-packages-registry/working-with-the-container-registry) is built into every GitHub repository and requires no additional setup. Authenticate with the built-in `GITHUB_TOKEN`: + +```bash title="Bash — Authenticate and push to GHCR" +echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin +docker tag myapp-api:latest ghcr.io/$GITHUB_REPOSITORY/myapp-api:$GITHUB_SHA +docker push ghcr.io/$GITHUB_REPOSITORY/myapp-api:$GITHUB_SHA +``` + + + For a complete end-to-end example including GHCR login and `aspire do push`, see [Step 3: Build app, create & push image to GHCR](/fundamentals/app-lifecycle/#step-3-build-app-create--push-image-to-ghcr). + + +### Docker Hub + +```bash title="Bash — Authenticate and push to Docker Hub" +echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin +docker tag myapp-api:latest $DOCKERHUB_USERNAME/myapp-api:$IMAGE_TAG +docker push $DOCKERHUB_USERNAME/myapp-api:$IMAGE_TAG +``` + +Store `DOCKERHUB_TOKEN` and `DOCKERHUB_USERNAME` as secrets in your pipeline. Generate the token from [hub.docker.com](https://hub.docker.com) under **Account settings > Personal access tokens**. + +### Other registries + +Other container registries (Amazon ECR, Azure Container Registry, Google Artifact Registry, JFrog Artifactory, and self-hosted registries) follow the same pattern: authenticate with `docker login`, tag your image with the registry hostname, and push. Refer to your registry's authentication documentation for the specific login command. + +## GitHub Actions workflow + +The following complete workflow generates artifacts with `aspire publish`, pushes images to GitHub Container Registry, and applies a Docker Compose deployment. Customize the deploy step for your own target (Kubernetes, a cloud provider, etc.). + +```yaml title="GitHub Actions — .github/workflows/deploy.yml" +name: Build and deploy Aspire app + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + packages: write # Required to push to GHCR + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 30 + + env: + IMAGE_TAG: ${{ github.sha }} + REGISTRY: ghcr.io/${{ github.repository_owner }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.x' + + - name: Install Aspire CLI + run: | + curl -sSL https://aspire.dev/install.sh | bash + echo "$HOME/.aspire/bin" >> $GITHUB_PATH + + - name: Generate deployment artifacts + # Adjust the --project path to match your AppHost project location + run: aspire publish --project src/AppHost/AppHost.csproj -o ./artifacts + + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Push container images + run: | + # Replace 'myapp-api' and 'myapp-web' with the resource names from your AppHost. + # The image names produced by aspire publish match your AppHost resource names. + for image in myapp-api myapp-web; do + docker tag ${image}:latest ${REGISTRY}/${image}:${IMAGE_TAG} + docker push ${REGISTRY}/${image}:${IMAGE_TAG} + done + + - name: Deploy + run: | + # This example uses Docker Compose. Replace with kubectl apply, helm upgrade, + # or your cloud provider's CLI as needed for your deployment target. + export MYAPP_API_IMAGE=${REGISTRY}/myapp-api:${IMAGE_TAG} + export MYAPP_WEB_IMAGE=${REGISTRY}/myapp-web:${IMAGE_TAG} + docker compose -f artifacts/docker-compose.yml up -d +``` + + + +## Azure DevOps pipeline + +The following pipeline publishes and deploys an Aspire application using the same pattern. It uses generic Docker CLI commands to push to any container registry. + +```yaml title="Azure DevOps — azure-pipelines.yml" +trigger: + branches: + include: + - main + +pool: + vmImage: ubuntu-latest + +variables: + imageTag: $(Build.SourceVersion) + registry: $(REGISTRY_HOST)/$(REGISTRY_NAMESPACE) + +steps: + - task: UseDotNet@2 + displayName: Set up .NET + inputs: + packageType: sdk + version: '10.x' + + - script: | + curl -sSL https://aspire.dev/install.sh | bash + echo "##vso[task.prependpath]$HOME/.aspire/bin" + displayName: Install Aspire CLI + + - script: | + # Adjust the --project path to match your AppHost project location + aspire publish --project src/AppHost/AppHost.csproj -o $(Build.ArtifactStagingDirectory)/artifacts + displayName: Generate deployment artifacts + + - script: | + echo "$(REGISTRY_PASSWORD)" | docker login $(REGISTRY_HOST) \ + -u $(REGISTRY_USERNAME) --password-stdin + displayName: Log in to container registry + + - script: | + # Replace 'myapp-api' and 'myapp-web' with the resource names from your AppHost. + for image in myapp-api myapp-web; do + docker tag ${image}:latest $(registry)/${image}:$(imageTag) + docker push $(registry)/${image}:$(imageTag) + done + displayName: Push container images + + - script: | + # This example uses Docker Compose. Replace with kubectl apply, helm upgrade, + # or your cloud provider's CLI as needed for your deployment target. + export MYAPP_API_IMAGE=$(registry)/myapp-api:$(imageTag) + export MYAPP_WEB_IMAGE=$(registry)/myapp-web:$(imageTag) + docker compose -f $(Build.ArtifactStagingDirectory)/artifacts/docker-compose.yml up -d + displayName: Deploy + + - task: PublishBuildArtifacts@1 + displayName: Publish artifacts + inputs: + PathtoPublish: $(Build.ArtifactStagingDirectory)/artifacts + ArtifactName: deployment-artifacts +``` + +Set `REGISTRY_HOST`, `REGISTRY_NAMESPACE`, `REGISTRY_USERNAME`, and `REGISTRY_PASSWORD` as pipeline variables or a variable group. Mark credentials as secret so they're not logged. + +## 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="GitHub Actions — Disable color output" +env: + NO_COLOR: '1' +``` + + + + +```yaml title="Azure DevOps — Disable color output" +variables: + NO_COLOR: '1' +``` + + + + +### Timeouts + +Publishing and deploying Aspire apps can take several minutes. Set your pipeline's timeout high enough to allow for: + +- .NET container image builds +- Container registry image push (varies with image size and network speed) +- Provisioning and startup of deployment targets + + + + +```yaml title="GitHub Actions — job timeout" +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 30 +``` + + + + +```yaml title="Azure DevOps — job timeout" +jobs: + - job: deploy + timeoutInMinutes: 30 +``` + + + + +### Docker availability + +The Aspire CLI builds container images using the local Docker daemon during `aspire publish`. 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. + + + + +```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 + +- [Publishing and deployment overview](/deployment/overview/) +- [Deploy using the Aspire CLI (Azure Container Apps)](/deployment/azure/aca-deployment-aspire-cli/) +- [Deployment state caching](/deployment/deployment-state-caching/) +- [`aspire publish` command reference](/reference/cli/commands/aspire-publish/) +- [`aspire deploy` command reference](/reference/cli/commands/aspire-deploy/)