Claude-skill-registry dotnet-cicd-patterns
Implement CI/CD for .NET applications with GitHub Actions, Azure DevOps, Docker, Kubernetes deployments, automated testing, and release pipelines. Use when setting up continuous integration and deployment for .NET projects.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dotnet-cicd-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-dotnet-cicd-patterns && rm -rf "$T"
manifest:
skills/data/dotnet-cicd-patterns/SKILL.mdsource content
.NET CI/CD Patterns
Master CI/CD for .NET 8+ with GitHub Actions, Azure DevOps, Docker, and Kubernetes.
GitHub Actions - .NET CI/CD
# .github/workflows/dotnet-ci.yml name: .NET CI/CD on: push: branches: [ main, develop ] pull_request: branches: [ main ] env: DOTNET_VERSION: '8.0.x' AZURE_WEBAPP_NAME: myapp jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore --configuration Release - name: Test run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" - name: Upload coverage uses: codecov/codecov-action@v3 with: files: '**/coverage.cobertura.xml' build-docker: needs: build-and-test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | ghcr.io/${{ github.repository }}:latest ghcr.io/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max deploy-azure: needs: build-docker runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Azure Login uses: azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Deploy to Azure Web App uses: azure/webapps-deploy@v2 with: app-name: ${{ env.AZURE_WEBAPP_NAME }} images: 'ghcr.io/${{ github.repository }}:${{ github.sha }}'
Azure DevOps Pipeline
# azure-pipelines.yml trigger: branches: include: - main - develop pool: vmImage: 'ubuntu-latest' variables: buildConfiguration: 'Release' dotnetSdkVersion: '8.x' stages: - stage: Build displayName: 'Build and Test' jobs: - job: BuildJob steps: - task: UseDotNet@2 displayName: 'Use .NET SDK' inputs: version: $(dotnetSdkVersion) - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' - task: DotNetCoreCLI@2 displayName: 'Build' inputs: command: 'build' arguments: '--configuration $(buildConfiguration) --no-restore' - task: DotNetCoreCLI@2 displayName: 'Run tests' inputs: command: 'test' arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage"' publishTestResults: true - task: PublishCodeCoverageResults@1 displayName: 'Publish code coverage' inputs: codeCoverageTool: 'Cobertura' summaryFileLocation: '$(Agent.TempDirectory)/**/*coverage.cobertura.xml' - task: DotNetCoreCLI@2 displayName: 'Publish' inputs: command: 'publish' publishWebProjects: true arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 displayName: 'Publish artifacts' inputs: pathToPublish: '$(Build.ArtifactStagingDirectory)' artifactName: 'drop' - stage: Deploy displayName: 'Deploy to Azure' dependsOn: Build condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployJob environment: 'production' strategy: runOnce: deploy: steps: - task: AzureWebApp@1 displayName: 'Deploy to Azure Web App' inputs: azureSubscription: '$(azureSubscription)' appName: '$(webAppName)' package: '$(Pipeline.Workspace)/drop/**/*.zip'
Docker Multi-Stage Build
# Dockerfile FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src # Copy csproj and restore COPY ["src/MyApp.Api/MyApp.Api.csproj", "src/MyApp.Api/"] COPY ["src/MyApp.Application/MyApp.Application.csproj", "src/MyApp.Application/"] COPY ["src/MyApp.Domain/MyApp.Domain.csproj", "src/MyApp.Domain/"] COPY ["src/MyApp.Infrastructure/MyApp.Infrastructure.csproj", "src/MyApp.Infrastructure/"] RUN dotnet restore "src/MyApp.Api/MyApp.Api.csproj" # Copy everything and build COPY . . WORKDIR "/src/src/MyApp.Api" RUN dotnet build "MyApp.Api.csproj" -c Release -o /app/build # Publish FROM build AS publish RUN dotnet publish "MyApp.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false # Runtime FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final WORKDIR /app EXPOSE 80 EXPOSE 443 # Non-root user RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app USER appuser COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "MyApp.Api.dll"]
Kubernetes Deployment
# k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: api image: ghcr.io/myorg/myapp:latest ports: - containerPort: 80 env: - name: ASPNETCORE_ENVIRONMENT value: "Production" - name: ConnectionStrings__DefaultConnection valueFrom: secretKeyRef: name: myapp-secrets key: db-connection-string resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health/live port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 80 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: myapp spec: selector: app: myapp ports: - protocol: TCP port: 80 targetPort: 80 type: LoadBalancer
Database Migrations in CI/CD
# .github/workflows/db-migration.yml name: Database Migration on: workflow_dispatch: push: branches: [main] paths: - 'src/**/Migrations/**' jobs: migrate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' - name: Install EF Core tools run: dotnet tool install --global dotnet-ef - name: Apply migrations run: | dotnet ef database update \ --project src/MyApp.Infrastructure \ --startup-project src/MyApp.Api \ --connection "${{ secrets.DB_CONNECTION_STRING }}" env: ASPNETCORE_ENVIRONMENT: Production
Automated Versioning
# Using GitVersion - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0 with: versionSpec: '5.x' - name: Determine Version uses: gittools/actions/gitversion/execute@v0 id: gitversion - name: Display version run: | echo "Version: ${{ steps.gitversion.outputs.semVer }}" echo "AssemblyVersion: ${{ steps.gitversion.outputs.assemblySemVer }}" - name: Build with version run: dotnet build -p:Version=${{ steps.gitversion.outputs.semVer }}
Release Management
# GitHub Release - name: Create Release if: github.ref == 'refs/heads/main' uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: v${{ steps.gitversion.outputs.semVer }} release_name: Release v${{ steps.gitversion.outputs.semVer }} body: | Changes in this release: ${{ github.event.head_commit.message }} draft: false prerelease: false
Best Practices
- Separate Build and Deploy - Build once, deploy many
- Use Secrets Management - Never hardcode credentials
- Implement Health Checks - For readiness and liveness probes
- Version Everything - Code, containers, deployments
- Test Before Deploy - Run all tests in CI
- Use Caching - Cache NuGet packages and Docker layers
- Monitor Deployments - Track deployment success rates