Skip to content

[ci-coach] CI/CD pipeline optimization opportunities #506

Description

@willvelida

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 lintvalidate 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 ·

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions