👋Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.
CI/CD for .NET and Vue: YAML pipelines, secrets, and deploy to Azure.
June 27, 2024 · Waqas Ahmad
Read the article
Introduction
This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).
Teams that skip clear CI/CD pipelines ship slower and hit more production incidents; build, test, and deploy in stages with environments and approval gates. This article compares Azure DevOps and GitHub Actions and outlines pipeline patterns for .NET and front ends (Vue, Angular) so you can build, test, and deploy to Azure with confidence—from a minimal pipeline to a full multi-stage setup with secrets and approval gates. For architects and tech leads, getting pipelines clear, secure, and repeatable matters for fast, safe releases.
System scale: One or more apps (e.g. .NET APIs, Vue/Angular front ends) building and deploying to Azure; from single pipeline to multi-stage with dev/staging/production. Applies when you want repeatable, secure releases.
Team size: One to several teams; someone must own pipeline definition, secrets, and environments. Works when dev and ops (or platform) can agree on stages and approval gates.
Time / budget pressure: Fits greenfield and existing repos; breaks down when there’s no time to move secrets out of code or to add approval gates—then at least get build and test automated first.
Technical constraints: Azure DevOps or GitHub Actions; .NET and/or front-end build tooling; Azure (App Service, AKS, etc.) for deploy. Assumes you can store secrets in a vault (e.g. Azure Key Vault, GitHub secrets).
Non-goals: This article does not optimize for on-prem only, for non-Azure targets, or for “no approval gates”; it optimises for Azure-hosted CI/CD with .NET and front ends.
What is CI/CD and why it matters
CI (continuous integration) means automatically building and testing code every time someone pushes or opens a pull request. The goal is to catch broken changes quickly and keep the main branch healthy. Without CI, broken code can sit in the repo until someone discovers it in production; with CI, the pipeline fails in minutes and the team fixes it before merging.
CD (continuous delivery or deployment) means automatically deploying a build to one or more environments (e.g. dev, staging, production). Continuous delivery produces a releasable artifact and can deploy to production, but the actual production deploy may be manual (e.g. a person clicks deploy or approves). Continuous deployment means every successful build is automatically deployed to production (often with approval gates). Most teams use continuous delivery with approval gates for production so that releases are repeatable and controlled.
Why it matters: Reliable CI/CD reduces mean time to release, catches bugs before production, and keeps environments consistent. When pipelines are clear and secure, teams ship faster and with fewer incidents. This article explains how to design and run CI/CD with Azure DevOps or GitHub Actions for .NET and front ends on Azure.
CI/CD at a glance
Concept
What it is
CI (continuous integration)
Automatically build and test on every push or PR; fail fast so broken changes are caught quickly.
Automation defined in YAML: stages (e.g. Build, Deploy), jobs (e.g. BuildAndTest), steps (e.g. dotnet build). Versioned with the code.
Stage
Coarse phase (e.g. Build, Deploy). Stages can depend on other stages (e.g. Deploy depends on Build).
Job
Runs inside a stage; one or more steps. Jobs in a stage can run in parallel.
Step
Single action (e.g. run a script, use a task, deploy).
Environment
Named target (e.g. dev, staging, production) with optional approval gates and protection rules.
Artifact
Output of the build (e.g. .NET publish folder, front-end dist) published so deploy stages use the same build.
Secrets
Connection strings, API keys, certs—stored in a vault (e.g. Azure Key Vault) or pipeline secret store; never in code or plain YAML.
Loading diagram…
What are pipelines? Stages, jobs, and steps
A pipeline is the automation that runs your CI/CD: a series of stages, jobs, and steps defined in YAML (e.g. Azure DevOps azure-pipelines.yml, GitHub Actions .github/workflows/deploy.yml) so that they are versioned with the code.
Stage: A coarse phase, e.g. Build (compile, test, publish artifact) and Deploy (deploy to dev, then staging, then production). Stages can depend on other stages (e.g. Deploy depends on Build). Use stages to separate build from deploy so that the same artifact is deployed everywhere—you do not rebuild per environment.
Job: A unit of work inside a stage. For example, the Build stage might have one job BuildAndTest that restores, builds, and runs tests. The Deploy stage might have one job per environment (DeployDev, DeployStaging, DeployProd) or one deployment job that targets an environment (e.g. environment: production). Jobs in a stage can run in parallel when they have no dependency on each other.
Step: A single action: run a script, use a built-in task (e.g. UseDotNet@2, AzureWebApp@1), or call a GitHub Action. Steps run sequentially within a job.
Why split build and deploy: Building and deploying in one giant job makes debugging hard and reuse impossible. If you deploy to dev, staging, and production from the same pipeline, you want one build (one artifact) deployed to all three—so build once in a Build stage, publish the artifact, then in the Deploy stage consume that artifact for each environment. That way you are guaranteed the same binaries and assets everywhere.
Azure DevOps vs GitHub Actions
Aspect
Azure DevOps
GitHub Actions
Where
Azure DevOps (dev.azure.com); repos can be Azure Repos or GitHub.
GitHub; workflows run on GitHub-hosted or self-hosted runners.
Pipeline definition
YAML in repo (azure-pipelines.yml) or classic editor.
YAML in .github/workflows/*.yml.
Triggers
Push, PR, schedule, manual.
Push, PR, schedule, workflow_dispatch, etc.
Azure integration
Service connections (ARM, Key Vault, ACR, App Service); approval gates and environments out of the box.
Use Azure/login (service principal or OIDC) and Azure/* actions (e.g. webapps-deploy); environments with protection rules and optional approvals.
Secrets
Variable groups, Key Vault linked to variable group, secret variables.
Repository/organization secrets; OIDC for Azure so no long-lived client secret.
When to use
Organisation already on Azure DevOps; need deep Azure integration and approval gates with minimal setup.
Repo on GitHub; want event-driven, YAML-based pipelines and a large marketplace of actions.
Azure DevOps provides Pipelines, Repos, Boards, and Artifacts in one place. Pipelines integrate tightly with Azure (service connections, Key Vault, App Service, AKS). Use Azure DevOps when your organisation is already on it or when you need deep Azure integration and approval gates out of the box.
GitHub Actions is event-driven (push, PR, schedule) and runs in GitHub-hosted or self-hosted runners. It is popular for open source and teams that host code on GitHub. Use Azure/login and Azure/webapps-deploy (or similar) to deploy to Azure. Both support environments and secrets; choose based on where your repo lives and what your team knows.
Pipeline structure: from minimal to full
We build up from a minimal pipeline (build only) to a full pipeline (build, test, publish artifact, deploy with environment). Each step adds one concept so you see how the pieces fit together.
Step 1: Minimal pipeline (build only)
# azure-pipelines.yml (Azure DevOps) or .github/workflows/build.yml (GitHub Actions)# Minimal: one job, restore + build. No test, no artifact, no deploy.jobs:-job:Buildsteps:-task:UseDotNet@2inputs:packageType:'sdk'version:'8.x'-script:dotnetrestore&&dotnetbuild--no-incrementaldisplayName:'Restore and build'
What this does: One job that restores NuGet packages and builds the solution. Use this to confirm the repo builds in the cloud. No tests, no artifact, no deploy yet.
What this does: Same job now runs tests (so broken code fails the pipeline) and publishes the build output as an artifact named drop. Downstream deploy jobs will consume drop so that the same build is deployed to every environment. (In a real pipeline you would add a dotnet publish step before publishing the artifact; see .NET pipeline below.)
Step 3: Add deploy stage with environment
stages:-stage:Buildjobs:-job:BuildAndTeststeps:-task:UseDotNet@2inputs:packageType:'sdk'version:'8.x'-script:dotnetrestore&&dotnetbuild&&dotnettest--no-build-task:PublishPipelineArtifact@1inputs:targetPath:'$(Build.SourcesDirectory)/publish'artifact:'drop'publishLocation:'pipeline'-stage:DeploydependsOn:Buildjobs:-deployment:DeployToProdenvironment:production# Approval gate can be set on this environmentstrategy:runOnce:steps:-task:AzureWebApp@1inputs:azureSubscription:'My-ServiceConnection'appName:'my-api'package:'$(Pipeline.Workspace)/drop'
What this does:Build stage runs restore, build, test, and publishes the artifact. Deploy stage depends on Build and runs a deployment job that targets the productionenvironment. In Azure DevOps, you can add approvals and checks on the production environment so that a human (or automated check) must approve before the deploy runs. The deploy step uses a service connection (azureSubscription) and never stores credentials in YAML.
How this fits together: The pipeline is now multi-stage: build once, publish one artifact, then deploy that artifact to production (with optional approval). The same pattern extends to dev and staging: add more deployment jobs or use environments (dev, staging, production) so that each has its own approval and config. Secrets (e.g. connection strings) stay in Key Vault or variable groups; the pipeline references them as variables and never logs them.
.NET pipeline: full example
Below is a full Azure DevOps–style YAML pipeline for a .NET API: restore, build, test, publish, then deploy to Azure App Service. Each section has a short comment so you can map it to the concepts above.
What this file is: A complete pipeline that builds the .NET solution (restore, build, test), publishes the API as a zip artifact, and deploys it to Azure App Service in the Deploy stage. AzureServiceConnection and WebAppName would be set as variables (or in a variable group) so that you can change them per environment without editing YAML. The environment: production ensures approval gates (if configured) run before deploy.
Front-end pipeline: Vue or Angular
For a Vue or Angular (or React) front end, the pattern is the same: build in one stage, publish the dist (or equivalent) as an artifact, then deploy to Static Web Apps, Blob + CDN, or the same host as your API.
# Build stage: install, lint, buildjobs:-job:BuildFrontEndsteps:-task:NodeTool@0inputs:versionSpec:'20.x'-script:|
npm ci
npm run lint
npm run build
displayName:'Install, lint, build'-task:PublishPipelineArtifact@1inputs:targetPath:'$(System.DefaultWorkingDirectory)/dist'artifact:'frontend'publishLocation:'pipeline'
What this does:npm ci installs dependencies from lockfile (reproducible). Lint and build produce the dist folder; that folder is published as artifact frontend. A separate deploy job (or stage) would consume frontend and deploy to Azure Static Web Apps or Blob storage. Use environment-specific config (e.g. API URL, tenant ID) via variables or build-time env so that the front end points to the right backend in each environment.
Secrets and variables
Secrets (connection strings, API keys, certificates) must never live in source control or in plain YAML. Use a vault (e.g. Azure Key Vault) or the pipeline’s secret store and reference them as variables.
Azure DevOps: Create a variable group and link it to Key Vault (or add secret variables in the group). In YAML, reference with $(MySecret). Mark variables as secret so they are not logged.
GitHub Actions: Add secrets in repository or organization settings. In the workflow, use ${{ secrets.MY_SECRET }}. For Azure, prefer OIDC (federated credential) so the workflow does not need a long-lived client secret.
Environment-specific config: Use variables (or variable groups) per environment so that connection strings, API URLs, and feature flags differ per environment. Never hardcode prod URLs or secrets in YAML; use variable templates or variable groups so that each environment gets the right config at deploy time.
Approval gates and environments
Environments (e.g. dev, staging, production) let you apply approval gates and protection rules so that production deploys are controlled.
Azure DevOps: Create an environment (e.g. production) in Pipelines → Environments. Add approvals and checks: require one or more approvers before the deployment job runs. In YAML, set environment: production on the deployment job; approvers get a notification and must approve before the job proceeds.
GitHub Actions: Create an environment (e.g. production) in the repo settings. Add protection rules: required reviewers, wait timer. In the workflow, set environment: production on the job; GitHub will wait for approval before running.
Use approval gates for production so that at least one person (or automated check) approves production deploys. For dev (and optionally staging), you can skip approvals so that every successful build deploys automatically.
Common issues and challenges
Secrets in code or YAML: Never store connection strings, API keys, or certificates in source control. Use Azure Key Vault or the pipeline’s secret store; reference secrets as variables and never log them.
Certificate and TLS failures in production: Wrong TLS version, expired certs, or misconfigured bindings cause hard-to-debug failures. Use managed certificates where possible; define rotation and expiry monitoring for custom certs. Document TLS requirements (e.g. TLS 1.2 minimum) for APIs and identity providers.
No approval gates for production: Deploying straight to production without review increases risk. Use environments with approval gates so that at least one person (or automated check) approves production deploys.
Flaky tests blocking the pipeline: Unreliable tests slow the team and erode trust. Quarantine flaky tests and fix them; use retries sparingly and only for known transient failures. Prefer deterministic tests and test doubles for external dependencies.
Build and deploy coupling: Building and deploying in one giant job makes debugging hard and reuse impossible. Split into stages: build (and publish artifact), then deploy (per environment). Use artifacts so that the same build is deployed everywhere.
Missing contract tests: Frontend and backend can drift if there are no API contract tests. Use OpenAPI diff or Pact in CI so that breaking changes fail the build before merge.
Best practices
Build once, deploy everywhere: One build stage, one artifact; deploy that artifact to dev, staging, and production. Do not rebuild per environment.
Secrets in a vault: Key Vault (or pipeline secrets); reference as variables; never echo or log.
Approval gates for production: Use environments with required approvers for production.
Stages for clarity: Separate Build and Deploy; use deployment jobs and environment so that approval and history are clear.
Pin task/action versions: In Azure DevOps pin task versions; in GitHub Actions pin actions to full SHA to avoid supply-chain surprises.
Cache restore: Use caching for NuGet/npm so that repeated runs are faster.
Quarantine flaky tests: Do not let flaky tests block the pipeline; fix or quarantine and fix later.
Summary
CI/CD means building and testing on every push/PR and deploying in stages; pipelines are YAML-defined (stages, jobs, steps)—build once, publish artifact, deploy that artifact to dev, staging, and production with environments and approval gates for production. Skipping approval gates or putting secrets in code leads to security and release failures; using a vault and clear stages keeps releases fast and safe. Next, define your stages and environments, add approval gates for production, and keep secrets in Key Vault or the pipeline secret store; use the FAQs below as a quick reference when designing and operating CI/CD.
CI = automatically build and test on every push/PR; CD = automatically deploy to environments. Pipelines are YAML-defined automation: stages (Build, Deploy), jobs, steps.
Build once, publish artifact, deploy that artifact to dev, staging, and production. Use environments and approval gates for production.
Secrets in Key Vault or pipeline secret store; never in code or plain YAML. Variables (or variable groups) for environment-specific config.
Azure DevOps vs GitHub Actions: choose based on where your repo lives and what your team knows; both support stages, environments, and Azure deploy.
Avoid: secrets in code, missing approval gates, flaky tests blocking the pipeline, build and deploy in one giant job, missing contract tests.
Position & Rationale
I use Azure DevOps when the org is already on it and we need environments, approval gates, and release tracking in one place; I use GitHub Actions when the repo is on GitHub and we want pipelines next to code. I avoid mixing both for the same app—pick one and stick to it so ownership is clear. I always put secrets in a vault (Key Vault, GitHub/Azure secrets), never in YAML or code. I favour multi-stage (build → test → deploy to dev → deploy to staging → approve → production) so that production never gets an untested build. I avoid skipping approval gates for production “to move faster”—one bad deploy costs more than the delay.
Trade-Offs & Failure Modes
What this sacrifices: Some setup time (YAML, environments, secrets); you also accept that production deploys are gated and not instant.
Where it degrades: When secrets or env vars are scattered (some in pipeline, some in Key Vault, some in app config) so that nobody knows the single source of truth. It also degrades when approval gates are bypassed or when every PR triggers a full production deploy.
How it fails when misapplied: No tests in the pipeline, or tests that are flaky and ignored; then “CI passing” is meaningless. Another failure: production deploy from a branch that isn’t main, or secrets in logs.
Early warning signs: “We deploy from our laptops sometimes”; “secrets are in the repo”; “we don’t know who approved the last production release.”
What Most Guides Miss
Most guides show a minimal pipeline and stop. The hard part is environments and approval gates: production should require an explicit approver (or group) and use a defined service connection or secret; that way you have an audit trail. The other gap: failure handling. When deploy to staging fails, the pipeline should fail loudly and not deploy to production; and you need a rollback story (e.g. redeploy previous artifact). Finally: .NET and front end in one repo often need two jobs (e.g. build backend, build front end) and sometimes different triggers; document who owns which part so changes to one don’t break the other silently.
Decision Framework
If greenfield and repo on GitHub → Use GitHub Actions; define build, test, deploy stages; add environments for staging and production with approval.
If org uses Azure DevOps → Use Azure Pipelines (YAML); same idea—stages, secrets in vault, approval gates for production.
If you have secrets in code or in pipeline YAML → Move to Key Vault or GitHub/Azure secrets; rotate and audit.
If production deploys are manual or ad hoc → Introduce at least one approval gate and a single path (pipeline) to production.
If build or test is flaky → Fix or quarantine; don’t let “we re-run until it passes” become the norm.
Pick one CI/CD system (Azure DevOps or GitHub Actions) per app; use multi-stage pipelines with build, test, deploy to dev/staging, then approval, then production.
Secrets live in a vault (Key Vault, GitHub/Azure secrets), never in YAML or code; audit and rotate.
Production deploy goes through the pipeline only, with an explicit approval gate; no deploy from laptops or ad hoc.
Flaky tests must be fixed or quarantined; re-running until green hides real failures.
Document rollback (e.g. redeploy previous artifact) and who owns pipeline changes.
For production-grade Azure systems, I offer consulting on cloud architecture, scalability, and cloud-native platform design.
When I Would Use This Again — and When I Wouldn’t
I would use this approach again when I’m setting up or refining CI/CD for .NET and/or front ends targeting Azure and the org can commit to one pipeline system and to approval gates for production. I wouldn’t use it when the target is not Azure (e.g. AWS, on-prem only)—then the patterns apply but tooling differs. I also wouldn’t use it when the org refuses to move secrets out of code or to gate production; then at least get build and test automated and push for gates later. Alternative: for tiny side projects, a single-stage “build and deploy to one environment” may be enough; add stages and approval as the app and team grow.
Frequently Asked Questions
Frequently Asked Questions
What are the core principles of CI/CD?
Automation: Build, test, and deploy should be repeatable and scripted. Fast feedback: Fail fast so that broken changes are caught in minutes. Security: Secrets in a vault; no credentials in code. Environments: Dev, staging, production with appropriate gates and config.
When should I use Azure DevOps vs GitHub Actions?
Use Azure DevOps when your organisation is already on it or when you need deep Azure integration (service connections, Key Vault, approval gates) out of the box. Use GitHub Actions when your repo is on GitHub and you want event-driven, YAML-based pipelines with a large marketplace of actions.
How do I manage secrets in CI/CD pipelines?
Use Azure Key Vault (or your cloud’s secret manager) and reference secrets as variables in the pipeline; never echo or log them. Use Managed Identity or service principal for pipeline identity so that no long-lived secrets are stored in the pipeline config.
What are common mistakes with production deployments?
Deploying without approval gates, missing rollback plan, and environment drift (config differs from dev to prod). Use approval gates, blue-green or slot swap for rollback, and configuration as code so that environments stay consistent.
How do I handle certificates in CI/CD?
Store certificates in Key Vault (or secret store); install them in the pipeline or on the target (e.g. App Service, AKS) via secure steps. Use managed certificates (e.g. App Service managed cert) where possible; for custom certs, define rotation and expiry alerts.
What is the role of contract tests in CI/CD?
Contract tests (e.g. OpenAPI diff, Pact) ensure that API changes do not break consumers. Run them in CI so that breaking changes fail the build before merge; this is especially important when frontend and backend are in separate repos or teams.
What is a pipeline stage vs job vs step?
A stage is a coarse phase (e.g. Build, Deploy). A job runs inside a stage (e.g. BuildAndTest). A step is a single action (e.g. run dotnet build). Stages can depend on other stages (e.g. Deploy depends on Build). Use stages to separate build from deploy so that the same artifact is deployed everywhere.
How do I deploy to Azure App Service from GitHub Actions?
Use Azure/login (service principal or OIDC) to authenticate, then Azure/webapps-deploy (or azure/appservice-deploy) to deploy the artifact (e.g. .NET publish output or zip). Store AZURE_WEBAPP_PASSWORD or use OIDC in a secret; never log credentials.
How do I use approval gates for production in Azure DevOps?
Create an environment (e.g. production) in Azure DevOps Pipelines and add approvals and checks: require one or more approvers before the deployment job runs. The deployment stage uses environment: production; approvers get a notification and must approve before the job proceeds.
What is blue-green deployment and when to use it?
Blue-green means two identical environments (blue and green); you deploy to the idle one, test it, then switch traffic to it (e.g. swap slots in App Service). Rollback is switching back. Use it when you want zero-downtime deploys and instant rollback. Azure App Service deployment slots are a form of blue-green.
How do I run tests in CI and keep the pipeline fast?
Run unit tests in the same job as build (fast feedback). Run integration tests in a separate job or stage (can be slower). Use parallel jobs when possible. Cache restore (e.g. NuGet, npm) so that repeated runs are faster. Quarantine flaky tests and fix them so they do not block the pipeline.
What is infrastructure as code (IaC) in CI/CD?
IaC means defining environments (e.g. Azure resources) in code (e.g. Bicep, Terraform) so that they are versioned and repeatable. In CI/CD, run IaC deploy (e.g. az deployment) in a stage so that environments are created or updated from code. Use separate state per environment so that prod is not accidentally overwritten.
How do I handle environment-specific config in CI/CD?
Use variables (pipeline variables, variable groups, or Key Vault references) per environment so that connection strings, API URLs, and feature flags differ per environment. Never hardcode prod URLs or secrets in YAML; use variable templates or variable groups so that each environment gets the right config.
What is a deployment slot in Azure App Service?
A deployment slot is a live app with its own hostname (e.g. myapp-staging.azurewebsites.net). You deploy to a slot (e.g. staging), test it, then swap it with production so that production gets the new code with minimal downtime. Rollback is swapping back. Use slots for staging and blue-green style deploys.
How do I secure my pipeline from supply chain attacks?
Use branch protection so that only PRs from trusted branches can trigger production deploys. Pin action versions to full SHA instead of @v1. Use OIDC or short-lived tokens instead of long-lived secrets where possible. Scan dependencies (e.g. Dependabot, Snyk) in CI so that vulnerable packages fail the build.
What is the difference between continuous delivery and continuous deployment?
Continuous delivery means the pipeline produces a releasable artifact and can deploy to production, but deployment may be manual (e.g. a person clicks deploy). Continuous deployment means every successful build is automatically deployed to production (with optional approval gates). Most teams use continuous delivery with approval gates for production.
Related Guides & Resources
Explore the matching guide, related services, and more articles.