---
name: ci-cd-pipeline-foundation
description: >-
  Cross-cutting CI/CD conventions: Linux-first runners when the stack allows
  it, explicit step identifiers, deployment environments, Azure Pipelines
  stages and variable groups, and GitHub Actions environments. Use when
  authoring or reviewing pipelines, choosing agents, or structuring build vs
  deploy for Azure DevOps or GitHub Actions.
---

# CI/CD pipeline foundation

## Linux-first runners

Before choosing **`windows-latest`** or **Windows** agents:

1. **Inspect the repo**: project type, Docker base images, scripts, native dependencies, path separators, and tooling (e.g. **.NET** often builds on Linux unless the solution targets **Windows-only** APIs, **WPF**, certain **COM** dependencies, or **MSBuild** steps that require Windows).
2. If the **default build/test/publish** path is **portable** (or already uses **`ubuntu`** in Dockerfile / docs), **prefer Linux** pool images (**`ubuntu-latest`**, **`ubuntu-22.04`**, etc.) for **build** and **deploy** jobs when deploy steps are also Linux-friendly (e.g. **bash**, **rsync**, **Docker**).

Reserve **Windows** agents for genuine Windows-only work; do not default to Windows out of habit.

**Self-hosted Linux** runners are common: **`runs-on: [self-hosted, Linux]`** (extra labels vary by fleet). The same **Linux-first** and **bash** deploy patterns apply; ensure required tools (**`git`**, **`rsync`**, **`dotnet`**, **`node`**) exist on the image.

## Step identifiers

- **GitHub Actions:** set **`id:`** on **every** step that runs in a job (required for **`steps.<id>.outputs`**, **`if:`** conditions, and readable maintenance). Use **stable snake_case** ids (e.g. **`id: restore`**, **`id: publish_artifacts`**).
- **Azure Pipelines:** set **`name:`** on **every** step (and **`displayName:`** for humans). Treat **`name`** as the step’s stable handle in the same way as GHA **`id`**.

## Environments

Use **deployment environments** whenever the platform supports them so approvals, secrets, and protection rules attach to **named targets** (e.g. **Staging**, **Production**), not only to branch filters.

- **GitHub Actions:** **`jobs.<job_id>.environment`** with **`name:`** and optional **`url:`** (e.g. **`url: ${{ env.PUBLIC_SITE_URL }}`** so the run links to the deployed site).
- **Azure DevOps:** **`deployment`** jobs with **`environment: '<name>'`** inside **`strategy.runOnce.deploy.steps`** (or appropriate strategy).

Prefer **environment-scoped** variables/secrets over ad hoc job variables when values differ per target.

## Azure DevOps: stages and variables

**Stages**

- Prefer **`stages`** (e.g. **Build**, **Deploy_Production**) over a long flat **`jobs`** list when separating **CI** from **CD** or multiple phases.
- Use **`dependsOn`**, **`condition`**, and per-stage **`pool`** so **build** and **deploy** are explicit.
- Use **`deployment`** jobs for deploy stages and wire **`environment`** as above.

**Variables**

- **Variable groups** in YAML: `variables:` with `- group: myapp-global` entries—shared, cross-pipeline values (URLs, feature flags, naming conventions, library-linked secrets).
- **Local pipeline variables**: `- name: csproj` / `value: ...` for paths and names **only for this pipeline** (e.g. **`csproj`**, **`sln`**, artifact folder names) that should not live in a shared group.

Keep **secrets** in **variable groups** or **secret** variables, never committed in YAML.

## Azure DevOps: runtime parameters and template expressions

- **`parameters:`** — expose **`Environment`**, feature toggles, or image tags at **queue time**; pair with **`values:`** for dropdowns.
- **Conditional compile-time variables** — set **`variables:`** entries with **`${{ if eq(parameters.Environment, 'Production') }}:`** / **`value:`** blocks so service connections, repo names, or **`.env`** file names switch per target (see [reference.md](reference.md)).
- **`condition:`** on **stages** — e.g. **`and(succeeded(), eq('${{ parameters.Environment }}', 'Development'))`** or branch checks with **`variables['Build.SourceBranch']`**.
- **`DownloadSecureFile@1`** — inject per-environment secrets (e.g. **`.env`**) into the workspace before **Docker** or **build** steps; reference **`$(mySecureFile.secureFilePath)`** from the task **`name`**.
- **`batch: true`** on **`trigger`** — collapse multiple commits into one CI run when appropriate.

## GitHub Actions alignment

- Use **`jobs.<job>.environment`** for deploy jobs when possible.
- Prefer **`runs-on: ubuntu-latest`** when the workflow is Linux-safe; use **self-hosted** labels when required by policy or tooling.
- Mirror the same **step `id`** discipline and **build vs deploy** job split; use **`needs:`** instead of ADO **`dependsOn`** where applicable.
- **`concurrency:`** — use a stable **`group:`** and **`cancel-in-progress: true`** (or `false`) so overlapping pushes do not pile up redundant deploys.
- **`on.push.paths`** — scope workflows to **`frontend/**`** (or similar) when only part of the repo should trigger a deploy.
- **Repository `vars`** — surface non-secret build-time values (e.g. **`VITE_*`** for Vite) via **`${{ vars.VITE_* }}`** in **`env:`** on build steps.

## Detailed YAML shape (Azure)

See [reference.md](reference.md) for a **stages + variable groups + deployment environment** skeleton (generic names, no real product monikers).
