Build a CI/CD Pipeline with Azure DevOps

Automatically deploy a website to Azure App Service using Azure DevOps YAML pipelines.

Introduction

30 Second Summary

Every time a developer changes code, someone has to get it onto the live website. Doing this manually is slow, error-prone, and one of the first things teams automate.

In this project, you will build an automated CI/CD pipeline using Azure DevOps that deploys a website to Azure App Service every time you push code to GitHub.

What You'll Build

A complete multi-stage YAML pipeline that watches your GitHub repository and automatically deploys a static website to Azure App Service on every code push.

By the end of this project, you'll have:

  • A live website on Azure App Service with a public URL you can share.
  • An Azure DevOps YAML pipeline with Build and Deploy stages that runs automatically on every push.
  • Hands-on experience with service connections, pipeline triggers, and deployment tasks.
  • Secret Mission: Add HTML validation and a manual approval gate for production-grade quality control.

Are there any prerequisites?

You need a GitHub account and a basic understanding of what code is. No prior experience with Azure, pipelines, or DevOps is required.

Install Git and Azure CLI

Before you can build a pipeline, you need the tools that make it possible. Git tracks your code changes and connects your local project to GitHub.

The Azure CLI lets you create and manage Azure resources directly from your terminal. In this step, you will install both tools and verify they are working.

In this step, get ready to:
  • Install and verify Git.
  • Install and verify Azure CLI.
  • Configure your Git identity for GitHub.
Check if Git is installed

Git may already be on your machine. Let's check before installing anything.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check if Git is already installed by running this command:
git --version

Look at the output in your terminal.

✔️ I see a version number

Git is already installed. Skip ahead to the next substep where you install Azure CLI.

ⓧ Command not found

Git is not installed yet. Follow the instructions below for your operating system.

macOS

  • Install Git using Homebrew by running this command:
brew install git
  • After installation completes, verify Git is available by running:
git --version

You should see a version number printed in your terminal.

Windows

  • Install Git using winget by running this command in PowerShell:
winget install --id Git.Git -e --source winget
  • Close and reopen PowerShell after the installation finishes.
  • Verify Git is available by running:
git --version

You should see a version number printed in your terminal.

Still not seeing a version number?

On macOS, if you do not have Homebrew, install it from brew.sh first, then re-run the install command.

On Windows, make sure you closed and reopened PowerShell after installing. The git command is only available in new terminal sessions.

Help me install Git on my computer

Install Azure CLI

The Azure CLI is a command-line tool that lets you create, configure, and delete Azure resources without leaving your terminal. You will use it throughout this project to deploy your website.

  • Install Azure CLI by running the command for your operating system:

macOS

brew update && brew install azure-cli

Windows

winget install --exact --id Microsoft.AzureCLI

After the install completes, close and reopen PowerShell.

  • Verify the installation by running:
az version

You should see output showing the Azure CLI version number and its extensions.

✔️ I see a version number

Azure CLI is installed and ready. Move on to the next substep.

ⓧ Command not found

Make sure you closed and reopened your terminal after the installation completed. The az command only becomes available in new terminal sessions.

If you still see the error after reopening, try running the install command again.

What is Azure CLI?

Azure CLI is a cross-platform tool that manages Azure resources through commands you type in your terminal. Instead of clicking through the Azure Portal UI, you can create servers, databases, and web apps with a single command.

In this project, you will use it to create an App Service and deploy your website code.

Verify your GitHub connection

Git needs to know your name and email so it can label your commits. These details are attached to every change you push to GitHub.

  • Check if your name is already configured by running:
git config --global user.name
  • Check if your email is already configured by running:
git config --global user.email

If both commands print your name and email, you are all set. If either command prints nothing (a blank line), you need to set them.

✔️ I see my name and email

Your Git identity is already configured. You are ready to move on to the next step.

ⓧ One or both are blank

  • Set your Git username by running this command (replace the placeholder with your actual name):
git config --global user.name "Your Name"
  • Set your Git email by running this command (replace the placeholder with the email linked to your GitHub account):
git config --global user.email "your-email@example.com"
  • Confirm both are saved by running the check commands again:
git config --global user.name
git config --global user.email

You should see your name and email printed back to you.

Why does Git need my name and email?

Every Git commit is signed with an author name and email. This is how GitHub knows which commits belong to your account.

Use the same email address that is associated with your GitHub account so your contributions are linked correctly.

Your local tools are ready to go. Next up, you will create your Azure DevOps organization and connect it to an Azure subscription so your pipeline has somewhere to deploy.

Set Up Azure DevOps and Azure Subscription

Your local tools are ready. Now you need the cloud accounts that power your pipeline.

Azure DevOps is where your pipeline lives. Azure App Service is where your website lives. In this step, you'll create accounts for both services and connect them to your terminal.

In this step, get ready to:
  • Create an Azure DevOps organization and project.
  • Set up an Azure pay-as-you-go subscription.
  • Request free parallelism for your pipeline (if needed).
Create an Azure DevOps organization

Azure DevOps is a platform that hosts your CI/CD pipelines, repositories, and project boards. You'll sign up with your existing GitHub account.

  • Open your browser and go to dev.azure.com.
  • Click Start free (or Sign in to Azure DevOps).
  • Select Sign in with GitHub.
  • Enter your GitHub credentials and authorize Microsoft-corp when prompted.

What happens during sign-in?

Azure DevOps uses your GitHub account to create a new organization automatically. An organization is the top-level container that holds all your projects.

After signing in, Azure DevOps prompts you to create your first project.

  • Enter my-website-pipeline as the project name.
  • Leave the visibility set to Private.
  • Click Create project.

You should see the my-website-pipeline project dashboard with sections for Boards, Repos, and Pipelines.

Don't see the project dashboard?

Make sure you authorized the GitHub connection when prompted. If the page shows an error, try refreshing or signing in again at dev.azure.com.

Help me troubleshoot my Azure DevOps sign-up. I authorized GitHub but I'm not seeing my project dashboard.

Set up your Azure pay-as-you-go subscription

Azure DevOps runs your pipeline for free. But deploying your website to Azure App Service requires an Azure subscription. The App Service F1 tier is always free, so you won't be charged.

  • Open a new browser tab and go to portal.azure.com.
  • Sign in with the same account you used for Azure DevOps (your GitHub-linked Microsoft account).
  • If prompted to create a subscription, select Pay-As-You-Go and follow the sign-up wizard.
  • Enter your phone number and credit or debit card when asked.

Will I be charged?

A credit card is required to verify your identity, but the App Service F1 plan costs $0. You will not be charged for anything in this project as long as you stay on the F1 tier.

Now connect your terminal to the subscription so the Azure CLI can create resources on your behalf.

  • Sign into Azure from your terminal by running this command:
az login

What does this command do?

Running az login opens your browser for an authorization code flow. You sign in through the browser, and the CLI receives a token that authenticates all future commands against your subscription.

After signing in through the browser, your terminal displays a JSON list of your subscriptions. You should see your pay-as-you-go subscription in the output.

Not seeing a subscription in the output?

If the output shows an empty list, your subscription may not be fully activated yet. Wait a few minutes, then run az login again.

If you see a browser error during sign-in, make sure pop-ups are not blocked for the Microsoft login page.

Help me troubleshoot my az login. I'm not seeing my subscription in the terminal output.

Request free parallelism grant

New Azure DevOps organizations sometimes start with zero parallel jobs. This means your pipeline would sit in a queue forever instead of running.

Check whether you need to request the free grant.

  • Go back to your Azure DevOps tab at dev.azure.com.
  • Click Organization Settings in the bottom-left corner.
  • In the left sidebar, expand Pipelines and select Parallel jobs.
  • Look at the Microsoft-hosted row. If it shows 0 parallel jobs, click the link to request a free grant.
  • Fill out the request form and submit it.

How long does the grant take?

The free grant typically takes 2-3 business days to process. You can continue setting up everything else in the meantime. Your pipeline will queue and run automatically once the grant is approved.

If the Microsoft-hosted row already shows 1 or more parallel jobs, you're all set and can skip the request.

Can't find Parallel jobs in settings?

Make sure you're in Organization Settings (not Project Settings). Organization Settings appears at the very bottom of the left sidebar when you're on the main Azure DevOps page.

Help me find the parallel jobs setting in Azure DevOps.

Your cloud accounts are ready. Next up, you'll create a website and push it to GitHub so your pipeline has something to deploy.

Create Your Website and Push to GitHub

Your Azure DevOps project and Azure subscription are set up. Now you need something to actually deploy.

In this step, you'll create a static HTML/CSS website and push it to GitHub. This gives your pipeline a source repository to watch for changes.

In this step, get ready to:
  • Create a static HTML/CSS website in VS Code.
  • Initialize a Git repository and commit your code.
  • Push your website to a GitHub repository.
Create a simple HTML/CSS project

Your pipeline needs a website to build and deploy. You'll create a simple HTML page with some CSS styling that explains how the pipeline works.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir my-azure-website
cd my-azure-website
  • Confirm the folder exists by running:
ls

The folder will be empty for now. You'll add files to it next.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the my-azure-website folder on your Desktop, then click Open.

Why open the folder this way?

Opening a folder (not just a file) tells VS Code to treat it as your project workspace. The Explorer sidebar on the left will show all files inside my-azure-website, making it easy to manage your project.

  • In the VS Code Explorer sidebar (the file icon panel on the left), right-click in the empty area.
  • Select New File.
  • Type index.html and press Enter.
  • Paste the following code into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Azure Website</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <header>
        <h1>Hello from Azure DevOps!</h1>
        <p>This site is automatically deployed by my CI/CD pipeline.</p>
    </header>
    <main>
        <section class="info-card">
            <h2>How This Works</h2>
            <p>Every time I push code to GitHub, Azure DevOps automatically builds and deploys this website to Azure App Service.</p>
        </section>
        <section class="info-card">
            <h2>Pipeline Stages</h2>
            <ul>
                <li>Build: Archives the website files into a ZIP artifact</li>
                <li>Deploy: Sends the artifact to Azure App Service</li>
            </ul>
        </section>
    </main>
    <footer>
        <p>Deployed with Azure DevOps Pipelines</p>
    </footer>
</body>
</html>

What does this code do?

This HTML page creates a simple website with a blue header, two info cards, and a footer. The content describes how your CI/CD pipeline works.

The <link rel="stylesheet" href="styles.css"> tag connects this page to a CSS file you'll create next for visual styling.

  • Save index.html with Cmd+S (macOS) or Ctrl+S (Windows).

File saved in the wrong place?

Make sure index.html appears directly under my-azure-website in the Explorer sidebar. If it ended up nested inside a subfolder, drag it to the top level of the project.

Help me make sure my file is in the right location in VS Code.

  • Right-click in the Explorer sidebar again and select New File.
  • Type styles.css and press Enter.
  • Paste the following code into styles.css:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f5f5f5;
}

header {
    background-color: #0078d4;
    color: white;
    padding: 3rem 2rem;
    text-align: center;
}

header h1 {
    font-size: 2.5rem;
    margin-bottom: 0.5rem;
}

header p {
    font-size: 1.2rem;
    opacity: 0.9;
}

main {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1rem;
}

.info-card {
    background: white;
    border-radius: 8px;
    padding: 2rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.info-card h2 {
    color: #0078d4;
    margin-bottom: 1rem;
}

.info-card ul {
    padding-left: 1.5rem;
}

.info-card li {
    margin-bottom: 0.5rem;
}

footer {
    text-align: center;
    padding: 2rem;
    color: #666;
}

What does this code do?

This CSS resets default browser margins, then applies a clean layout with a blue header (#0078d4 is Azure's brand color), white info cards with subtle shadows, and readable typography.

The .info-card class creates a card-style container with rounded corners and a drop shadow. This makes the content look polished without any frameworks.

  • Save styles.css with Cmd+S (macOS) or Ctrl+S (Windows).
  • Open index.html directly in your browser by double-clicking it in Finder (macOS) or File Explorer (Windows) on your Desktop inside the my-azure-website folder.

You should see a blue header with "Hello from Azure DevOps!" and two white info cards below it on a light gray background.

Page looks unstyled?

If the page is plain black text with no colors, your browser cannot find styles.css. Make sure both index.html and styles.css are in the same folder. Check the filename spelling matches exactly.

My HTML page has no styling. Help me fix it.

✔️ Awesome, I've got everything!

Great. Make sure both files are saved in VS Code before moving on.

ⓧ I'd like to double check the full code

Here is the full index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Azure Website</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <header>
        <h1>Hello from Azure DevOps!</h1>
        <p>This site is automatically deployed by my CI/CD pipeline.</p>
    </header>
    <main>
        <section class="info-card">
            <h2>How This Works</h2>
            <p>Every time I push code to GitHub, Azure DevOps automatically builds and deploys this website to Azure App Service.</p>
        </section>
        <section class="info-card">
            <h2>Pipeline Stages</h2>
            <ul>
                <li>Build: Archives the website files into a ZIP artifact</li>
                <li>Deploy: Sends the artifact to Azure App Service</li>
            </ul>
        </section>
    </main>
    <footer>
        <p>Deployed with Azure DevOps Pipelines</p>
    </footer>
</body>
</html>

Here is the full styles.css:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f5f5f5;
}

header {
    background-color: #0078d4;
    color: white;
    padding: 3rem 2rem;
    text-align: center;
}

header h1 {
    font-size: 2.5rem;
    margin-bottom: 0.5rem;
}

header p {
    font-size: 1.2rem;
    opacity: 0.9;
}

main {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1rem;
}

.info-card {
    background: white;
    border-radius: 8px;
    padding: 2rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.info-card h2 {
    color: #0078d4;
    margin-bottom: 1rem;
}

.info-card ul {
    padding-left: 1.5rem;
}

.info-card li {
    margin-bottom: 0.5rem;
}

footer {
    text-align: center;
    padding: 2rem;
    color: #666;
}
Initialize a Git repository

Before you can push code to GitHub, you need to turn your project folder into a Git repository. This tracks changes to your files over time and prepares them for upload.

  • Open your terminal in VS Code by pressing Ctrl+` (backtick key, above Tab on most keyboards).
  • Make sure the terminal shows you're inside my-azure-website. You should see my-azure-website in the terminal prompt.
  • Initialize a Git repository by running this command:
git init
  • Stage all your files for the first commit by running:
git add .
  • Create your first commit by running:
git commit -m "Initial commit"

What do these commands do?

git init creates a hidden .git folder that turns my-azure-website into a Git repository.

git add . stages all files (the dot means "everything in this folder"). git commit saves a snapshot of those staged files with a message describing the change.

You should see output mentioning the files that were committed, including index.html and styles.css.

Seeing 'Author identity unknown'?

This means Git does not have your name and email configured. You set this up in Step 1. Run git config --global user.name to check. If it's blank, set it again with the commands from Step 1.

Help me fix the author identity error in Git.

Push your code to GitHub

Your code is committed locally. Now you need a remote repository on GitHub so that Azure DevOps can access it.

  • Open your browser and go to github.com.
  • Click the + icon in the top right corner and select New repository.
  • Enter my-azure-website as the repository name.
  • Leave the repository set to Public.
  • Do NOT check any of the initialization options (no README, no .gitignore, no license). You already have files locally.
  • Click Create repository.

Why leave the repo empty?

You already have a local Git repository with files committed. If GitHub adds its own README or .gitignore, you'll get merge conflicts when you try to push. Starting with an empty remote avoids this.

GitHub will show a quick setup page with instructions. You'll use the commands below instead.

  • Back in your VS Code terminal, connect your local repo to GitHub by running this command:
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/my-azure-website.git
  • Rename your branch to main and push your code by running these commands:
git branch -M main
git push -u origin main

What do these commands do?

git remote add origin tells Git where your remote repository lives on GitHub.

git branch -M main ensures your default branch is named main (some systems default to master). git push -u origin main uploads your commit to GitHub and sets origin main as the default push target.

  • Refresh your GitHub repository page in the browser.

You should see index.html and styles.css listed in the repository with your "Initial commit" message.

Push was rejected or asked for a password?

If Git asks for a username and password, GitHub no longer accepts password authentication. You need a Personal Access Token (PAT) or to use the GitHub CLI for authentication.

If the push was rejected due to conflicts, make sure you did NOT check any initialization options when creating the repository on GitHub.

Help me fix my GitHub push authentication issue.

Your website code is now on GitHub and ready for Azure DevOps to access. Next up, you'll create the Azure App Service that will host your live website.

Create the Azure App Service

Your website code is on GitHub. Now you need a destination for it on the internet so anyone can visit your work at a public URL.

Azure App Service is a managed hosting platform that handles servers for you. You'll create one on the free tier and deploy your site with a single CLI command.

In this step, get ready to:
  • Create an Azure resource group to organize your cloud resources.
  • Deploy your website to Azure App Service on the free tier.
  • Verify your website is live at a public URL.
Create a resource group

In Azure, a resource group is a container that holds related resources together. You need one before you can create an App Service.

  • Create a resource group called rg-my-website by running this command:
az group create --name rg-my-website --location eastus

What does this command do?

  • az group create tells the Azure CLI to create a new resource group.
  • --name rg-my-website sets the name so you can reference it in later commands.
  • --location eastus places your resources in the East US data center.
  • You should see JSON output in your terminal with "provisioningState": "Succeeded" confirming the resource group was created.

Seeing an error?

  • If you see a message about running az login first, your CLI session has expired. Run az login and sign in again.
  • If you see an error about your subscription, make sure your pay-as-you-go subscription is active in the Azure Portal.
  • Why is my az group create command failing?
Deploy your website to App Service

The az webapp up command creates the App Service plan, the web app, and deploys your code all in one go.

  • Make sure your terminal is inside the my-azure-website folder (the one with your index.html and styles.css).
  • Deploy your website by running this command:
az webapp up --name [[APPNAME="my-website-yourname123"]] --resource-group rg-my-website --sku F1 --html

What does this command do?

  • az webapp up creates and deploys an App Service in one command.
  • --name sets a globally unique name for your web app. This becomes part of your public URL, so it must be unique across all of Azure.
  • --resource-group rg-my-website places the app in the resource group you just created.
  • --sku F1 selects the free tier so you pay nothing.
  • --html tells Azure this is a static HTML site.

Pick a unique app name

Your app name must be globally unique across all of Azure. Try adding your name and a few numbers, like my-website-sarah42 or my-website-dev2025.

  • The command takes a minute or two to complete. When it finishes, you should see JSON output containing a URL that looks like https://my-website-yourname123.azurewebsites.net.
  • Copy this URL from your terminal output. You'll need it in a moment.

Deployment failed?

  • If you see a message that the webapp name is not available, your chosen name is already taken. Try a different combination of letters and numbers.
  • If you see a zip deployment error, make sure you ran the command from inside your my-azure-website folder where index.html lives.
  • Why is my az webapp up command failing?
Verify your live website
  • Open your browser and paste the URL from the terminal output.

You should see your HTML page live on the internet. The blue header with "Hello from Azure DevOps!" confirms your deployment worked.

What should I see?

Your website should look exactly like it did locally. A blue header bar with white text, two info cards explaining how the pipeline works, and a footer at the bottom.

Page not loading?

  • If you see a default Azure placeholder page, the deployment may still be processing. Wait 30 seconds and refresh.
  • If you see a 404 error, make sure your index.html file is in the root of your my-azure-website folder (not in a subfolder).
  • Why can't I see my website on the Azure App Service URL?

Your website is live on the internet. Next, you'll connect Azure DevOps to both GitHub and Azure so the pipeline can automate everything.

Connect Azure DevOps to GitHub and Azure

Your website is live on Azure App Service. Now it's time to connect the dots so Azure DevOps can automatically deploy your code whenever you push changes to GitHub.

Azure DevOps needs two connections to make this work. One to your GitHub repository (where the code lives) and one to your Azure subscription (where the website lives). Without these connections, the pipeline has no way to pull your code or deploy it.

In this step, get ready to:
  • Create a service connection to Azure.
  • Create the pipeline and connect to GitHub.
  • Confirm both connections are working.
Create a service connection to Azure

A service connection is how Azure DevOps authenticates with external services. Your pipeline will need permission to deploy code to your App Service, so you need a connection between Azure DevOps and your Azure subscription.

  • Open your my-website-pipeline project in Azure DevOps at dev.azure.com.
  • Click the Project settings gear icon in the bottom-left corner of the sidebar.
  • Under the Pipelines section in the left menu, click Service connections.
  • Click New service connection.
  • Select Azure Resource Manager from the list, then click Next.
  • Select Service principal (automatic) and click Next.
  • For Scope level, select Subscription.
  • Select your pay-as-you-go subscription from the Subscription dropdown.
  • Select rg-my-website from the Resource group dropdown.
  • In the Service connection name field, type azure-connection.
  • Check the Grant access permission to all pipelines checkbox.
  • Click Save.

What is a service connection?

A service connection stores credentials that let Azure DevOps interact with an external service on your behalf. By choosing Service principal (automatic), Azure DevOps creates an app registration in your Azure Active Directory and grants it Contributor access to your resource group.

The name azure-connection is what your YAML pipeline will reference later using the azureSubscription field in deployment tasks.

Seeing a permissions error?

If Azure DevOps cannot create the service principal, your Azure account may not have Owner permissions on the subscription. Ask your subscription owner to grant you the Owner role, or have them create the service connection for you.

help me fix a permissions error when creating an Azure DevOps service connection

Create the pipeline and connect to GitHub

Now you need to create the pipeline itself and point it at your GitHub repository. This tells Azure DevOps where to find your code and where to store the pipeline definition file.

  • In the left sidebar of your Azure DevOps project, click Pipelines.
  • Click New pipeline (or Create pipeline if this is your first pipeline).
  • On the "Where is your code?" screen, select GitHub.

What happens when you select GitHub?

Azure DevOps will redirect you to GitHub to authorize the Azure Pipelines GitHub app. This app allows Azure DevOps to read your repositories and receive push notifications. Click Approve & install if prompted.

  • If prompted, authorize the Azure Pipelines app by clicking Approve & install.
  • On the "Select a repository" screen, select your my-azure-website repository.
  • On the "Configure your pipeline" screen, select Starter pipeline.
  • Do NOT click "Save and run" yet. You will replace this starter YAML content in the next step.
  • Click Save (from the dropdown next to "Save and run") to save the pipeline without running it.

Why choose Starter pipeline?

The "Starter pipeline" option gives you a minimal placeholder azure-pipelines.yml file. You will replace its contents with your own multi-stage pipeline in the next step. Choosing a different template would add unnecessary complexity.

Don't see your repository?

If my-azure-website does not appear in the repository list, the Azure Pipelines GitHub app was not authorized. Go back and click Approve & install when GitHub prompts you. Your repository must be in your personal GitHub account for it to appear.

help me connect my GitHub repository to Azure DevOps Pipelines

Confirm the connections

Before moving on, verify that both connections are healthy.

  • Go to Project settings (gear icon, bottom-left).
  • Click Service connections under the Pipelines section.
  • Confirm that azure-connection appears in the list with a green checkmark.
  • In the left sidebar, click Pipelines to return to the Pipelines page.
  • Confirm that your new pipeline is listed and shows the my-azure-website repository connection.

Both connections are live. Next up, you will write the actual YAML pipeline that uses these connections to build and deploy your website automatically.

Write and Run the YAML Pipeline

In the previous step, you connected Azure DevOps to both your GitHub repository and your Azure subscription. Now it's time to write the actual pipeline that automates your deployments.

A YAML pipeline is a file that tells Azure DevOps exactly what to do when new code arrives. You will define two stages: a Build stage that packages your website files, and a Deploy stage that sends them to your live App Service.

In this step, get ready to:
  • Replace the starter YAML with a multi-stage pipeline definition.
  • Save and run the pipeline to trigger its first execution.
  • Verify your website is live via the automated deployment.
Replace the starter pipeline YAML

The starter pipeline Azure DevOps created in the previous step is just a placeholder. You will replace it entirely with a proper multi-stage pipeline.

  • In Azure DevOps, go to Pipelines in the left sidebar.
  • Click on your pipeline, then click Edit in the top right corner.
  • Select all the existing YAML content in the editor (Cmd+A on macOS or Ctrl+A on Windows) and delete it.
  • Paste the following YAML into the editor:
# Trigger the pipeline on every push to the main branch
trigger:
  - main

# Use a Microsoft-hosted Ubuntu agent
pool:
  vmImage: 'ubuntu-latest'

stages:
  # Stage 1: Package the website files into a ZIP artifact
  - stage: Build
    displayName: 'Build Stage'
    jobs:
      - job: BuildJob
        displayName: 'Archive Website'
        steps:
          - task: ArchiveFiles@2
            displayName: 'Archive website files'
            inputs:
              rootFolderOrFile: '$(Build.SourcesDirectory)'
              includeRootFolder: false
              archiveType: 'zip'
              archiveFile: '$(Build.ArtifactStagingDirectory)/website.zip'
              replaceExistingArchive: true

          - task: PublishPipelineArtifact@1
            displayName: 'Publish artifact'
            inputs:
              targetPath: '$(Build.ArtifactStagingDirectory)/website.zip'
              artifactName: 'drop'

  # Stage 2: Deploy the ZIP artifact to Azure App Service
  - stage: Deploy
    displayName: 'Deploy Stage'
    dependsOn: Build
    condition: succeeded()
    jobs:
      - job: DeployJob
        displayName: 'Deploy to App Service'
        steps:
          - task: DownloadPipelineArtifact@2
            displayName: 'Download artifact'
            inputs:
              artifactName: 'drop'
              targetPath: '$(Pipeline.Workspace)/drop'

          - task: AzureWebApp@1
            displayName: 'Deploy to Azure App Service'
            inputs:
              azureSubscription: 'azure-connection'
              appType: 'webApp'
              appName: '[[APPNAME="your unique App Service name"]]'
              package: '$(Pipeline.Workspace)/drop/website.zip'
              deploymentMethod: 'auto'
  • In the appName field, replace the placeholder with your actual App Service name (the one you created in the previous step, e.g. my-website-abc123).

What does this YAML do?

This pipeline has two stages that run in sequence:

  • trigger: - main tells Azure DevOps to run this pipeline every time you push code to the main branch.
  • The Build stage uses ArchiveFiles@2 to zip your website files and PublishPipelineArtifact@1 to save the ZIP so the next stage can access it.
  • The Deploy stage downloads the ZIP artifact, then uses AzureWebApp@1 to push it to your live App Service using the service connection you created earlier.
  • dependsOn: Build ensures the Deploy stage waits for Build to finish successfully before running.

Why two stages instead of one?

Separating Build and Deploy mirrors how production pipelines work. The Build stage creates a versioned artifact that can be deployed to multiple environments.

If deployment fails, you can re-deploy the same artifact without rebuilding. This pattern also makes it easy to add approval gates between stages later (which you will do in the Secret Mission).

Save and run the pipeline

With your YAML in the editor, you can now commit it to your repository and trigger the first run in one action.

  • Click Save and run in the top right corner of the editor.
  • A dialog appears asking you to commit the azure-pipelines.yml file to your repository.
  • Leave the default commit message and branch (main) as they are.
  • Click Save and run again to confirm.

What happens when you save and run?

Azure DevOps commits the azure-pipelines.yml file directly to your GitHub repository on the main branch. Because the pipeline triggers on pushes to main, this commit immediately kicks off the first pipeline run.

  • Watch the pipeline run in the Azure DevOps UI. You will see the Build Stage run first, followed by the Deploy Stage.
  • Click on each stage to expand it and see the individual tasks completing in real time.
  • Wait for both stages to show green checkmarks. This typically takes 1-2 minutes.

Pipeline stuck on queuing?

If your pipeline stays in the "Queued" state for more than a few minutes, your organization may not have a free parallel job granted yet. Go to Organization Settings > Pipelines > Parallel jobs and check if Microsoft-hosted shows 0.

If it shows 0, submit a request for the free tier grant. This can take 2-3 business days to be approved. Once approved, your queued pipeline will automatically start.

Help me troubleshoot my queued pipeline

Verify the deployment and sync locally
  • Once both stages show green checkmarks, open a new browser tab.
  • Visit your App Service URL (https:// followed by your app name and .azurewebsites.net).
  • You should see your website live with the heading "Hello from Azure DevOps!" and the blue header bar.

Your pipeline just deployed the website automatically. The same website you saw before is now being deployed through your pipeline instead of the manual az webapp up command.

  • Back in Azure DevOps, click on the completed pipeline run to see the run summary.
  • You should see both Build Stage and Deploy Stage with green checkmarks.

Now pull the new file to your local machine so your local folder stays in sync.

  • In your terminal, make sure you are inside the my-azure-website folder, then pull the changes by running:
git pull
  • Confirm azure-pipelines.yml now exists in your local folder by running:
ls
  • You should see azure-pipelines.yml, index.html, and styles.css listed.

Deploy stage failed?

Check that the appName in your YAML matches your actual App Service name exactly. You can find it in the Azure Portal under App Services.

Also verify that your service connection name in the YAML is azure-connection (case-sensitive). This must match the name you gave it in Project Settings.

Help me debug my failed Deploy stage

✔️ Awesome, I've got everything!

Great job. Make sure you have pulled the latest changes locally with git pull before moving on to the next step.

ⓧ I'd like to double check the full code

Here is the complete azure-pipelines.yml for comparison:

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: Build
    displayName: 'Build Stage'
    jobs:
      - job: BuildJob
        displayName: 'Archive Website'
        steps:
          - task: ArchiveFiles@2
            displayName: 'Archive website files'
            inputs:
              rootFolderOrFile: '$(Build.SourcesDirectory)'
              includeRootFolder: false
              archiveType: 'zip'
              archiveFile: '$(Build.ArtifactStagingDirectory)/website.zip'
              replaceExistingArchive: true

          - task: PublishPipelineArtifact@1
            displayName: 'Publish artifact'
            inputs:
              targetPath: '$(Build.ArtifactStagingDirectory)/website.zip'
              artifactName: 'drop'

  - stage: Deploy
    displayName: 'Deploy Stage'
    dependsOn: Build
    condition: succeeded()
    jobs:
      - job: DeployJob
        displayName: 'Deploy to App Service'
        steps:
          - task: DownloadPipelineArtifact@2
            displayName: 'Download artifact'
            inputs:
              artifactName: 'drop'
              targetPath: '$(Pipeline.Workspace)/drop'

          - task: AzureWebApp@1
            displayName: 'Deploy to Azure App Service'
            inputs:
              azureSubscription: 'azure-connection'
              appType: 'webApp'
              appName: 'my-website-UNIQUE'
              package: '$(Pipeline.Workspace)/drop/website.zip'
              deploymentMethod: 'auto'

Remember to replace my-website-UNIQUE with your actual App Service name.

Your pipeline is fully operational. Next up, you will make a code change and watch the entire CI/CD loop fire automatically.

Trigger CI/CD with a Code Change

Your pipeline is live and connected. The real magic of CI/CD is that you never have to manually deploy again.

In this step, you will change your website code, push it to GitHub, and watch the pipeline automatically detect the change, build, and deploy. This is the full CI/CD loop in action.

In this step, get ready to:
  • Make a visible change to your website in VS Code.
  • Push the change to GitHub.
  • Watch the pipeline auto-trigger and deploy the update to your live site.
Make a visible change to your website

To prove the pipeline works automatically, you need a change that is easy to spot when you refresh the live URL.

  • Open index.html in VS Code.
  • Find the <h1> line inside the <header> section:
<h1>Hello from Azure DevOps!</h1>
  • Change the text inside the <h1> tags to something different. For example, change it to My CI/CD Pipeline Works! or any text you like.
  • Save index.html.

Why change the heading?

Choosing a visible text change makes it obvious when the deployment succeeds. After the pipeline runs, you will refresh your live URL and immediately see whether the new text appeared.

Push the change to GitHub

Your pipeline is configured to trigger on every push to the main branch. Pushing this change is what starts the entire automation chain.

  • Stage, commit, and push your change by running these commands in your terminal:
git add .
git commit -m "Update homepage"
git push

What do these commands do?

  • git add . stages all changed files for commit.
  • git commit -m "Update homepage" saves the changes locally with a descriptive message.
  • git push sends the commit to GitHub, which triggers your Azure DevOps pipeline.

You should see output confirming the push to origin/main.

Push rejected or failed?

  • If you see a "rejected" error, the remote has changes you do not have locally. Run git pull --rebase first, then git push again.
  • If you see an authentication error, confirm you are still signed into GitHub by running git config --global user.name and checking that it returns your username.

Why is my git push failing?

Watch the pipeline auto-trigger

The moment your push lands on GitHub, Azure DevOps detects it and starts a new pipeline run. This is the CI/CD trigger in action.

  • Open your browser and go to your Azure DevOps project at dev.azure.com.
  • Click Pipelines in the left sidebar.
  • Look for a new run that has started automatically. It should show the commit message Update homepage.
  • Click on the run to watch its progress. You will see both the Build Stage and Deploy Stage execute in sequence.

What should I see?

The pipeline run page shows two stages as boxes. Each one turns green with a checkmark when it completes. The Build stage archives your updated files, and the Deploy stage pushes them to your App Service.

  • Once both stages show green checkmarks, open a new browser tab.
  • Visit your App Service URL (https://my-website-UNIQUE.azurewebsites.net, replacing UNIQUE with your actual value).
  • You should see your updated heading text live on the page.

Force a hard refresh

If you still see the old heading, your browser may be serving a cached version. Press Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to force a hard refresh.

Pipeline not triggering?

  • Confirm your push succeeded by checking your repository on github.com. The latest commit should say Update homepage.
  • Open azure-pipelines.yml and verify the trigger section says - main. If it says none, the trigger is disabled.
  • Check that the GitHub connection in Azure DevOps is still authorized. Go to Project Settings > Service connections and confirm both connections show green checkmarks.

Why isn't my Azure DevOps pipeline triggering after I push?

You just experienced the full CI/CD loop. A single git push triggered an automated build and deployment, and your live website updated without you touching a server. Next up is an optional Secret Mission to add quality gates to this pipeline.

Secret mission

Add HTML Validation and a Manual Approval Gate

Your pipeline currently pushes code straight to production with zero checks. Real teams add quality gates that catch broken code and require human sign-off before deployment. In this secret mission, you will add an HTML validation stage and a manual approval gate to your pipeline.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

This project uses Azure App Service on the F1 Free tier and Azure DevOps free-tier minutes, so there are no ongoing costs as long as you stay on these tiers.

Cost warning

If you ever change the App Service pricing tier away from F1, you will be charged. Always verify the tier is Free (F1) in the Azure Portal under your App Service > App Service Plan > Pricing tier.

Resources you used:

  • Azure App Service (F1 free tier).
  • Azure resource group (rg-my-website).
  • Azure DevOps organization and project.
  • GitHub repository (my-azure-website).
  • Local project folder (my-azure-website).
  • Azure CLI (installed via Homebrew).

Keep everything running

No action needed. Choose this if you are still actively building or want to keep your pipeline running.

  • The App Service on F1 costs nothing.
  • Your pipeline will continue to run on every push, using your 1,800 free minutes per month.
  • This is safe to leave running indefinitely.
  • Azure CLI remains installed on your machine for future Azure projects.

Pause - I'll come back to this later

Disable the pipeline trigger to stop automatic runs while keeping everything intact.

  • Open your azure-pipelines.yml file in VS Code.
  • Change trigger: from - main to none.
  • Commit and push the change.
  • This stops automatic pipeline runs while keeping all your Azure resources and code intact.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete all Azure resources (App Service, App Service Plan, resource group) by running this command:
az group delete --name rg-my-website --yes --no-wait
  • Delete your Azure DevOps project by going to dev.azure.com, clicking Organization settings, selecting Projects, clicking the ellipsis next to your project, and choosing Delete. Confirm by typing the project name.
  • Delete your GitHub repository by navigating to your repository on github.com, clicking Settings, scrolling down to the Danger Zone section, and clicking Delete this repository. Follow the confirmation prompts.
  • Delete your local project folder by running:
rm -rf ~/Desktop/my-azure-website
  • Uninstall the Azure CLI by running:
brew uninstall azure-cli

Nice Work!

Nice Work!

You just built a professional CI/CD pipeline from scratch. Every time you push code, it automatically builds, packages, and deploys to a live URL.

Here's what you learned:

  • Created a multi-stage YAML pipeline in Azure DevOps with Build and Deploy stages that package and ship your website automatically.
  • Connected Azure DevOps to GitHub (for triggers) and Azure (for deployment) using service connections.
  • Experienced the full CI/CD loop: push a code change, watch the pipeline auto-trigger, and see the update go live on your App Service URL.
  • Added HTML validation and a manual approval gate for production-grade quality control.

Ready to quiz yourself?