Skip to main content

.NET CI/CD Workflows

Generated by dotnet-ci-library, which is composed by all three .NET service archetypes:

  • dotnet-rest-service-archetype
  • dotnet-grpc-service-archetype
  • dotnet-graphql-service-archetype

The protocol has no effect on the pipeline - all three render the same two workflow files.

Build Workflow

.github/workflows/build.yaml, shown as rendered for a project named billing-service:

name: Build

on:
push:
branches: ["**"]
pull_request:

permissions:
contents: write
id-token: write

env:
IMAGE_NAME: billing-service
APPLICATION_NAME: billing-service

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: p6m-actions/dotnet-setup@v1

- name: Cut Patch Version
if: github.ref_name == 'main'
id: cut-patch
uses: p6m-actions/dotnet-cut-tag@v1
with:
version-level: patch

- name: Build .NET Application
uses: p6m-actions/dotnet-build@v1
with:
run-tests: true
publish-artifacts: ${{ github.ref_name == 'main' }}

# ... shared container build, release and dispatch steps

Step Chain

#StepScopeRuns on
1CheckoutSharedAlways
2.NET setup.NETAlways
3Cut Patch Version.NETmain
4Build .NET Application.NETAlways
5Login to Artifactory Container RegistrySharedmain
6Set up Docker BuildxSharedAlways
7Build and Publish Docker ImageSharedAlways
8Make ArtifactsSharedmain
9Create Github releaseSharedmain
10Update Application ManifestSharedmain

.NET is the only language whose build step takes a branch-dependent input: build artifacts are published as a workflow artifact on main and not on feature branches.

Language-Specific Steps

.NET Setup

- uses: p6m-actions/dotnet-setup@v1

p6m-actions/dotnet-setup installs the .NET SDK and restores NuGet packages with caching.

InputDefaultPassed by the workflow
dotnet-version8.0.x(default)
global-json-file(unset)(default)
cachetrue(default) - NuGet package caching
install-dependenciestrue(default) - runs dotnet restore
OutputDescription
dotnet-versionThe installed SDK version
cache-hitWhether the NuGet cache was restored
nuget-cache-dirPath to the NuGet cache
SDK 8.0.x is the action default

The workflow does not pin dotnet-version. If your project targets a different SDK, either add a global.json at the repository root (and pass global-json-file) or set dotnet-version explicitly, so CI and the production Dockerfile agree.

Cut Patch Version

- name: Cut Patch Version
if: github.ref_name == 'main'
id: cut-patch
uses: p6m-actions/dotnet-cut-tag@v1
with:
version-level: patch

p6m-actions/dotnet-cut-tag bumps the version in Directory.Build.props, commits, and creates a git tag.

InputRequiredValue
version-levelNopatch in build.yaml; the dispatch input in cut-tag.yaml
directory-build-props-pathNoDirectory.Build.props (default)
commit-changesNotrue (default)
commit-messageNoBump version to {version} [skip ci] (default)
skip-pushNofalse (default)
OutputDescription
versionThe new version, e.g. 1.4.2
tagThe created git tag

The action runs p6m-actions/token-exchange@v2 first, which authenticates the push as p6m-ybor[bot] and installs the [skip ci] commit hook. The default commit message also carries [skip ci] explicitly, so the bump is protected twice over.

Build .NET Application

- name: Build .NET Application
uses: p6m-actions/dotnet-build@v1
with:
run-tests: true
publish-artifacts: ${{ github.ref_name == 'main' }}

p6m-actions/dotnet-build restores, builds, optionally tests, and optionally uploads the publish output.

InputDefaultValue in the workflow
run-teststruetrue (explicit)
publish-artifactsfalse${{ github.ref_name == 'main' }} (explicit)
dotnet-version8.0(default)
solution-path.(default)
configurationRelease(default)
collect-coveragefalse(default)
artifact-namedotnet-build-artifacts(default)
verbosityminimal(default)
test-filter(empty)(default) - all tests run
OutputDescription
build-versionThe version of the built application
test-resultsTest execution summary
coverage-reportCoverage percentage, only when collect-coverage is enabled

None of these outputs are consumed by the generated workflow - the version that reaches the release and the image tag comes from cut-patch, not from build-version.

publish-artifacts and the container image are independent

The workflow artifact uploaded on main is a convenience for inspecting build output from the Actions UI. The container image is built separately from .platform/docker/prd/Dockerfile and does not consume it.

Cut Tag Workflow

.github/workflows/cut-tag.yaml:

name: Cut Tag

on:
workflow_dispatch:
inputs:
version-level:
description: "Version bump level"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major

permissions:
id-token: write
contents: write

jobs:
cut-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- uses: p6m-actions/dotnet-setup@v1

- uses: p6m-actions/dotnet-cut-tag@v1
with:
version-level: ${{ inputs.version-level }}
Inputversion-level - patch, minor, or major
OutputsA [skip ci] version bump commit in Directory.Build.props and a git tag, both pushed

Version File

Directory.Build.props at the repository root is the source of truth, so every project in the solution shares one version:

<Project>
<PropertyGroup>
<Version>1.4.2</Version>
</PropertyGroup>
</Project>