CI / build agents
CephalonEngine builds with standard dotnet build — there’s nothing engine-specific to wire into your pipeline. This page collects the practical patterns that make engine apps shippable from CI: tool-manifest restore, smoke-test gating, deployment-mode posture, and container builds.
Minimum pipeline shape
Section titled “Minimum pipeline shape”Every CephalonEngine CI run boils down to:
1. checkout2. setup .NET 10 SDK3. dotnet tool restore ← restores Cephalon.Cli from local manifest4. dotnet restore ← restores Cephalon.* packages5. dotnet build --no-restore6. dotnet test --no-build7. cephalon doctor --json ← (optional) hard-fail on toolchain driftSkip step 3 if you don’t use a local tool manifest. Skip step 7 if you’d rather not block on cephalon doctor warnings.
GitHub Actions
Section titled “GitHub Actions”name: buildon: push: branches: [main, develop] pull_request: branches: [main]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4 with: dotnet-version: 10.x
- name: Cache NuGet packages uses: actions/cache@v4 with: path: | ~/.nuget/packages ~/.dotnet/tools key: nuget-${{ runner.os }}-${{ hashFiles('**/Directory.Packages.props', '**/*.csproj', '.config/dotnet-tools.json') }} restore-keys: | nuget-${{ runner.os }}-
- run: dotnet tool restore # restores Cephalon.Cli from manifest - run: dotnet restore - run: dotnet build --no-restore -c Release - run: dotnet test --no-build -c Release --filter "Category!=Integration"
- name: cephalon doctor run: dotnet tool run cephalon doctor --json | tee doctor.json continue-on-error: true # warn but don't fail on first iteration
- name: Upload doctor report uses: actions/upload-artifact@v4 with: name: doctor-report path: doctor.jsonHardening: fail on cephalon doctor regressions
Section titled “Hardening: fail on cephalon doctor regressions”Once you have a green baseline, swap the soft continue-on-error: true for a hard fail:
- name: cephalon doctor (hard fail) run: | dotnet tool run cephalon doctor --json > doctor.json jq -e '.result == "ok"' doctor.jsonThis blocks merges if the SDK / runtime drifts from the supported band.
Azure Pipelines
Section titled “Azure Pipelines”trigger: [main]pool: { vmImage: 'ubuntu-latest' }
variables: buildConfiguration: 'Release'
steps: - task: UseDotNet@2 displayName: 'Install .NET 10 SDK' inputs: { version: '10.x' }
- task: Cache@2 inputs: key: 'nuget | "$(Agent.OS)" | **/Directory.Packages.props,**/*.csproj,.config/dotnet-tools.json' path: $(NUGET_PACKAGES) displayName: 'Cache NuGet packages'
- script: dotnet tool restore displayName: 'Restore tools'
- script: dotnet restore displayName: 'Restore packages'
- script: dotnet build --no-restore -c $(buildConfiguration) displayName: 'Build'
- script: dotnet test --no-build -c $(buildConfiguration) --logger trx --results-directory $(Agent.TempDirectory) displayName: 'Test'
- task: PublishTestResults@2 condition: succeededOrFailed() inputs: testRunner: VSTest testResultsFiles: '$(Agent.TempDirectory)/**/*.trx'Behind Azure Artifacts
Section titled “Behind Azure Artifacts”If your Cephalon.* packages live in a private Azure Artifacts feed, configure the credential provider:
- task: NuGetAuthenticate@1 displayName: 'Authenticate Azure Artifacts'Run this before dotnet restore. It populates the credential provider so dotnet restore can pull from the authenticated feed without prompting.
GitLab CI
Section titled “GitLab CI”image: mcr.microsoft.com/dotnet/sdk:10.0
stages: - build - test
variables: NUGET_PACKAGES: $CI_PROJECT_DIR/.nuget/packages DOTNET_CLI_TELEMETRY_OPTOUT: 1
cache: key: files: - Directory.Packages.props - .config/dotnet-tools.json paths: - .nuget/packages/ - .dotnet/tools/
build: stage: build script: - dotnet tool restore - dotnet restore - dotnet build --no-restore -c Release artifacts: paths: - "src/*/bin/Release/" expire_in: 1 hour
test: stage: test needs: [build] script: - dotnet test --no-build -c Release --logger "junit" artifacts: when: always reports: junit: "**/test-results.xml"Local Docker build
Section titled “Local Docker build”For environments without a hosted CI, or to validate locally:
docker build -t acmestore:dev .docker run --rm -p 8080:8080 acmestore:devThe generated Dockerfile uses mcr.microsoft.com/dotnet/sdk:10.0 for the build stage and mcr.microsoft.com/dotnet/aspnet:10.0 for runtime.
Alpine base images (smaller)
Section titled “Alpine base images (smaller)”FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS buildWORKDIR /srcCOPY . .RUN dotnet publish src/Acme.Store.Host/Acme.Store.Host.csproj -c Release -o /out
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS runtimeWORKDIR /appCOPY --from=build /out .ENTRYPOINT ["dotnet", "Acme.Store.Host.dll"]Alpine images are ~60% smaller than the default Debian-based images. Trade-off: some native dependencies need explicit apk add (most engine apps don’t).
Multi-arch builds
Section titled “Multi-arch builds”docker buildx build \ --platform linux/amd64,linux/arm64 \ -t acmestore:latest \ --push \ .ARM64 image runs on Apple Silicon, AWS Graviton, Azure Arm-based App Service. CephalonEngine has no native dependencies that block ARM64.
Composition smoke tests
Section titled “Composition smoke tests”The CLI-generated test project includes CompositionSmokeTests.cs — a fast (<1s) test that proves your modules compose without runtime errors. Run it in CI before any integration tests.
public sealed class CompositionSmokeTests{ [Fact] public void Modules_Compose_Without_Errors() { using var host = TestHostFactory.Create(); // boots full engine var manifest = host.Services.GetRequiredService<IEngineManifest>(); Assert.NotEmpty(manifest.Modules); }}In CI:
- run: dotnet test tests/Acme.Store.Tests --filter "FullyQualifiedName~Composition" --no-build -c ReleaseIf composition fails — missing capability, unresolved dependency, conflicting registrations — this test catches it before integration tests waste CI minutes spinning up databases.
Deployment-mode gating
Section titled “Deployment-mode gating”CephalonEngine apps can target multiple deployment modes (Self-contained, Trimmed, AOT, SingleFile). Each mode has its own constraints. Gate releases on the deployment-mode check:
- name: Verify deployment-mode posture run: | dotnet tool run cephalon doctor --json --deployment-mode SingleFile > posture.json jq -e '.deploymentMode.posture == "ok"' posture.jsonThe --deployment-mode <mode> flag tells cephalon doctor to validate against the constraints of that specific mode. Available: Default, SelfContained, Trimmed, Aot, SingleFile. See Reference → CLI → doctor.
Behind a corporate CI runner
Section titled “Behind a corporate CI runner”If your CI runs on self-hosted agents inside a corporate network, the same proxy / air-gap rules as workstations apply:
env: HTTP_PROXY: http://proxy.corp.acme:8080 HTTPS_PROXY: http://proxy.corp.acme:8080 NO_PROXY: localhost,127.0.0.1,nuget.acme.exampleIf your CI runner has no internet access, configure NuGet to read from an internal mirror:
- run: dotnet nuget add source $INTERNAL_NUGET_URL --name internal-mirror- run: dotnet restore --source internal-mirrorSee CLI install → Air-gapped install for the full air-gap workflow.
Tips & tricks
Section titled “Tips & tricks”Cache strategy
Section titled “Cache strategy”- Cache
~/.nuget/packages+~/.dotnet/tools. Cache key onDirectory.Packages.props+*.csproj+.config/dotnet-tools.json— invalidates only when versions change. - Don’t cache
bin/andobj/. They’re per-checkout and small enough that rebuild is faster than restore-from-cache. - Separate caches per OS.
nuget-linux-...andnuget-windows-...keys. CI providers cache per-runner-OS already, but explicit prefixes help debugging.
Parallelism
Section titled “Parallelism”- Run
dotnet testwith--parallelwhen your tests are isolated. Composition smoke tests are isolated (each test creates its ownTestHost). - Use job matrices for cross-platform. Build on
ubuntu-latestfor fast feedback; run the same suite onwindows-latestweekly to catch line-ending / path issues.
Speed tips
Section titled “Speed tips”- Pre-bake the .NET 10 SDK into your base CI image if you have one. Faster than
actions/setup-dotneton every build. - Use
--no-restoreand--no-buildin subsequent steps. Saves 20-40 seconds per build. - Run
dotnet build /m:1in CI if you hit memory pressure. Parallel build is faster but uses N× the RAM. dotnet build /p:UseSharedCompilation=falsein restricted CI environments — VBCSCompiler can hang on locked-down agents.
Failure-mode patterns
Section titled “Failure-mode patterns”- Always upload
doctor.jsonas a build artifact. Comparing across runs surfaces toolchain drift. - Always run composition smoke tests before integration tests. A 1-second composition failure beats a 5-minute integration failure.
- Set
--logger trx(Azure DevOps) or--logger junit(GitLab/Jenkins) so test results render natively in the UI. continue-on-error: trueforcephalon doctorduring initial onboarding — let the team see warnings before they fail merges.
Common pitfalls
Section titled “Common pitfalls”| Don’t | Do |
|---|---|
Skip dotnet tool restore because “we use the global CLI” | CI should be reproducible — local-manifest restore is required for that. |
dotnet build in every step (rebuilding from scratch) | Use --no-restore --no-build for downstream steps. |
Cache bin/ and obj/ | They’re per-build artifacts, not deps. Don’t waste cache size on them. |
| Hardcode the SDK version in every workflow | Pin once in global.json + use dotnet-version: 10.x in setup-dotnet — read from global.json automatically. |
| Run integration tests on every PR | Use a --filter "Category!=Integration" lane for fast PR builds; run integration on main post-merge. |
Where to go next
Section titled “Where to go next”- ① CLI install — the CLI your CI is restoring.
- ② Manual install — what your CI is building.
- Operations → Production readiness — what to gate on before going live.
- Reference → CLI → doctor —
cephalon doctorflag reference. - Tutorial → CI/CD pipeline — end-to-end pipeline walkthrough.