Summary
18 deployment pipelines and 10 reusable templates analyzed. Found 3 High, 4 Medium, and 5 Low impact optimization opportunities.
High Impact
❌ H1: No NuGet package caching
Affected: template-dotnet-run-unit-tests.yml, template-dotnet-run-contract-tests.yml, template-dotnet-run-e2e-tests.yml
Current state: Each template runs dotnet restore without caching. A typical pipeline triggers 3 independent restore operations (unit, contract, E2E). Across 18 pipelines, that's 54 uncached restores per full CI sweep.
Recommended fix: Add actions/cache for ~/.nuget/packages keyed on **/packages.lock.json or **/*.csproj hash in each test template.
Estimated savings: 30-60s per job × 3 jobs = ~2-3 min per pipeline run.
❌ H2: No Docker layer caching
Affected: template-acr-push-image.yml, template-ai-bom-push-image.yml
Current state: Both templates set up Docker Buildx but don't use its GHA cache backend. template-acr-push-image.yml uses raw docker build instead of docker/build-push-action with cache.
Recommended fix:
template-acr-push-image.yml: Replace docker build + docker push with docker/build-push-action using cache-from: type=gha / cache-to: type=gha,mode=max
template-ai-bom-push-image.yml: Add cache-from: type=gha and cache-to: type=gha,mode=max to the existing docker/build-push-action step
Estimated savings: 1-4 min per build (layer reuse on unchanged base images).
❌ H3: No concurrency groups on any deployment pipeline
Affected: All 18 deploy-*.yml pipelines
Current state: Zero pipelines have a concurrency block, allowing parallel deploys of the same service on concurrent PRs.
Recommended fix: Add to each pipeline:
concurrency:
group: deploy-{service}-${{ github.ref }}
cancel-in-progress: false
Estimated savings: Prevents failed deployments from resource contention and wasted runner minutes on superseded runs.
Medium Impact
⚠️ M1: No timeout-minutes on any job
Affected: All 18 deployment pipelines and 10 templates (0/28 files set timeout-minutes)
Current state: All jobs use GitHub's default 360-minute timeout. A hung Azure deployment or stuck emulator would burn 6 hours of runner time before failing.
Recommended fix: Add timeout-minutes: 15 to test/lint jobs, timeout-minutes: 30 to build/deploy jobs.
⚠️ M2: Missing path filter self-reference (8 pipelines)
Affected: deploy-activity-api.yml, deploy-activity-service.yml, deploy-core-infra.yml, deploy-food-api.yml, deploy-food-service.yml, deploy-sleep-api.yml, deploy-ui.yml, deploy-vitals-api.yml
Current state: These pipelines don't include .github/workflows/deploy-{name}.yml in their paths: filter. Changes to the workflow itself won't trigger a validation run.
Recommended fix: Add - '.github/workflows/deploy-{service}.yml' to each file's paths filter.
⚠️ M3: Missing preview (what-if) stage (3 pipelines)
Affected: deploy-chat-api.yml, deploy-reporting-api.yml, deploy-ui.yml
Current state: These skip the Bicep what-if preview, deploying directly after validate without showing infrastructure change impact.
Recommended fix: Add the preview job using template-bicep-whatif.yml between validate and deploy-dev.
⚠️ M4: Redundant retrieve-container-image-dev stage
Affected: All 15 app deployment pipelines (not core-infra or reporting-service-*)
Current state: After the build template already queries ACR (steps getacrname and getacrserver), a separate retrieve-container-image-dev job spins up a new runner, authenticates to Azure again, and runs the same az acr list query just to output loginServer. This adds ~1-2 min of overhead (runner provisioning + Azure login).
Recommended fix: Output loginServer from the build template job and consume it directly in downstream jobs.
Low Impact
💡 L1: env-setup job overhead
Affected: 15 pipelines with env-setup job
Current state: A dedicated runner job exists solely to propagate DOTNET_VERSION env var as a job output. This adds ~20-30s runner startup.
Recommended fix: Use a workflow-level env var directly in template inputs, or define it as a local env: in each calling job.
💡 L2: Inconsistent template reference style
Affected: deploy-ui.yml (uses ./.github/workflows/template-*.yml), deploy-vitals-api.yml (one local ref)
Current state: Most pipelines use willvelida/biotrackr/.github/workflows/template-*.yml@main but a few use the local ./ syntax. Both work, but the conventions file specifies uses: ./.github/workflows/template-{name}.yml.
Note: The majority use the fully-qualified form. Standardize on one style (local ./ is preferred per conventions).
💡 L3: Inconsistent coverage threshold
Affected: deploy-reporting-api.yml uses coverage-threshold: 60, all others use 70.
💡 L4: Orphaned template
Affected: template-aca-api-integration-tests.yml
Current state: Not referenced by any deployment pipeline. If unused, it should be removed to reduce maintenance burden.
💡 L5: Sequential lint → validate Bicep stages
Affected: All pipelines with Bicep stages
Current state: validate depends on lint, but linting and validation are independent operations (validation doesn't use lint output). Running them in parallel would save ~30-60s.
Note: Both depend on retrieve-container-image-dev for the image name, so they could run in parallel after that stage.
Conventions Compliance
| Convention |
Status |
Notes |
| Action pinning (SHA) |
✅ |
All third-party actions pinned to full SHA |
| Permissions (contents: read default) |
✅ |
All pipelines set minimal permissions |
| Concurrency groups |
❌ |
0/18 pipelines have concurrency block |
| Path filter self-reference |
⚠️ |
8/18 missing self-reference |
| OIDC authentication |
✅ |
All use federated identity |
| Template prefix naming |
✅ |
All templates use template- prefix |
| Template reference style |
⚠️ |
2 pipelines use local ./ vs fully-qualified |
| Secret handling |
✅ |
No secrets echoed or passed as CLI args |
| Environment naming |
✅ |
Uses dev environment |
| timeout-minutes |
❌ |
0/28 files set timeout-minutes |
Run Performance
No deployment pipeline runs found in the last 7 days. Performance data unavailable — pipelines may only trigger on PRs to main.
References:
- No recent workflow runs available for deep-dive analysis
Generated by CI Optimization Coach · ● 4.6M · ◷
Summary
18 deployment pipelines and 10 reusable templates analyzed. Found 3 High, 4 Medium, and 5 Low impact optimization opportunities.
High Impact
❌ H1: No NuGet package caching
Affected:
template-dotnet-run-unit-tests.yml,template-dotnet-run-contract-tests.yml,template-dotnet-run-e2e-tests.ymlCurrent state: Each template runs
dotnet restorewithout caching. A typical pipeline triggers 3 independent restore operations (unit, contract, E2E). Across 18 pipelines, that's 54 uncached restores per full CI sweep.Recommended fix: Add
actions/cachefor~/.nuget/packageskeyed on**/packages.lock.jsonor**/*.csprojhash in each test template.Estimated savings: 30-60s per job × 3 jobs = ~2-3 min per pipeline run.
❌ H2: No Docker layer caching
Affected:
template-acr-push-image.yml,template-ai-bom-push-image.ymlCurrent state: Both templates set up Docker Buildx but don't use its GHA cache backend.
template-acr-push-image.ymluses rawdocker buildinstead ofdocker/build-push-actionwith cache.Recommended fix:
template-acr-push-image.yml: Replacedocker build+docker pushwithdocker/build-push-actionusingcache-from: type=gha/cache-to: type=gha,mode=maxtemplate-ai-bom-push-image.yml: Addcache-from: type=ghaandcache-to: type=gha,mode=maxto the existingdocker/build-push-actionstepEstimated savings: 1-4 min per build (layer reuse on unchanged base images).
❌ H3: No concurrency groups on any deployment pipeline
Affected: All 18
deploy-*.ymlpipelinesCurrent state: Zero pipelines have a
concurrencyblock, allowing parallel deploys of the same service on concurrent PRs.Recommended fix: Add to each pipeline:
Estimated savings: Prevents failed deployments from resource contention and wasted runner minutes on superseded runs.
Medium Impact
timeout-minuteson any jobAffected: All 18 deployment pipelines and 10 templates (0/28 files set timeout-minutes)
Current state: All jobs use GitHub's default 360-minute timeout. A hung Azure deployment or stuck emulator would burn 6 hours of runner time before failing.
Recommended fix: Add
timeout-minutes: 15to test/lint jobs,timeout-minutes: 30to build/deploy jobs.Affected:
deploy-activity-api.yml,deploy-activity-service.yml,deploy-core-infra.yml,deploy-food-api.yml,deploy-food-service.yml,deploy-sleep-api.yml,deploy-ui.yml,deploy-vitals-api.ymlCurrent state: These pipelines don't include
.github/workflows/deploy-{name}.ymlin theirpaths:filter. Changes to the workflow itself won't trigger a validation run.Recommended fix: Add
- '.github/workflows/deploy-{service}.yml'to each file's paths filter.preview(what-if) stage (3 pipelines)Affected:
deploy-chat-api.yml,deploy-reporting-api.yml,deploy-ui.ymlCurrent state: These skip the Bicep what-if preview, deploying directly after validate without showing infrastructure change impact.
Recommended fix: Add the
previewjob usingtemplate-bicep-whatif.ymlbetween validate and deploy-dev.retrieve-container-image-devstageAffected: All 15 app deployment pipelines (not core-infra or reporting-service-*)
Current state: After the build template already queries ACR (steps
getacrnameandgetacrserver), a separateretrieve-container-image-devjob spins up a new runner, authenticates to Azure again, and runs the sameaz acr listquery just to outputloginServer. This adds ~1-2 min of overhead (runner provisioning + Azure login).Recommended fix: Output
loginServerfrom the build template job and consume it directly in downstream jobs.Low Impact
💡 L1:
env-setupjob overheadAffected: 15 pipelines with
env-setupjobCurrent state: A dedicated runner job exists solely to propagate
DOTNET_VERSIONenv var as a job output. This adds ~20-30s runner startup.Recommended fix: Use a workflow-level env var directly in template inputs, or define it as a local
env:in each calling job.💡 L2: Inconsistent template reference style
Affected:
deploy-ui.yml(uses./.github/workflows/template-*.yml),deploy-vitals-api.yml(one local ref)Current state: Most pipelines use
willvelida/biotrackr/.github/workflows/template-*.yml@mainbut a few use the local./syntax. Both work, but the conventions file specifiesuses: ./.github/workflows/template-{name}.yml.Note: The majority use the fully-qualified form. Standardize on one style (local
./is preferred per conventions).💡 L3: Inconsistent coverage threshold
Affected:
deploy-reporting-api.ymlusescoverage-threshold: 60, all others use70.💡 L4: Orphaned template
Affected:
template-aca-api-integration-tests.ymlCurrent state: Not referenced by any deployment pipeline. If unused, it should be removed to reduce maintenance burden.
💡 L5: Sequential
lint→validateBicep stagesAffected: All pipelines with Bicep stages
Current state:
validatedepends onlint, but linting and validation are independent operations (validation doesn't use lint output). Running them in parallel would save ~30-60s.Note: Both depend on
retrieve-container-image-devfor the image name, so they could run in parallel after that stage.Conventions Compliance
template-prefix./vs fully-qualifieddevenvironmentRun Performance
No deployment pipeline runs found in the last 7 days. Performance data unavailable — pipelines may only trigger on PRs to
main.References: