Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Azure Pipelines: Build Your First CI/CD Pipeline

By Kokil Thapa | Last reviewed: September 2026

You need a repeatable path from git push to a running site, and Azure Pipelines: Build Your First CI/CD Pipeline is the fastest way to get there inside Microsoft Azure DevOps. Unlike ad-hoc FTP uploads or manual SSH deploys, a YAML pipeline version-controls every build, test, and release step. If you already run GitLab CI for Laravel or Jenkins on a VPS, the concepts transfer cleanly—triggers, agents, artifacts, and environments just live under Azure DevOps names. This guide walks through a real PHP/Laravel-style pipeline you can copy, adapt, and ship today on PHP 8.3+ with Composer 2.10.

What do you need before you start Azure Pipelines: Build Your First CI/CD Pipeline?

Azure Pipelines sits inside Azure DevOps. You can use it free for small teams on public or private repos. Before writing YAML, gather four things: source control, a target server or Azure resource, secrets stored outside git, and a clear branch strategy.

Source control can live in Azure Repos, GitHub, or Bitbucket. Azure DevOps connects to external remotes with a service connection. Your deploy target might be an Ubuntu VPS over SSH, Azure App Service, or Azure Kubernetes Service (AKS). For Laravel 12 or 13.x apps, plan PHP 8.3 minimum (8.2 still works on Laravel 12), Composer 2.10, and optionally Node.js 26 LTS plus npm 12 if you compile front-end assets with Vite 8.x.

Azure Pipelines CI/CD OverviewGit Pushmain branchAzure DevOpsYAML pipelineBuild Agentubuntu-latestTest StagePHPUnit / lintDeploy TargetApp Service · AKS · SSHDeveloper commits codePipeline auto-runs
Azure Pipelines: Build Your First CI/CD Pipeline — from git push through build, test, and deploy targets

Prerequisites checklist

  1. An Azure DevOps organisation at dev.azure.com (free tier works for small teams).
  2. A repository with application code and a runnable test suite.
  3. Deploy credentials stored as pipeline variables or Azure Key Vault—not in YAML.
  4. An agent pool: Microsoft-hosted ubuntu-latest is fine for most PHP builds.

On client projects I maintain with Deployer 7 and GitLab CI, the mental model is identical. Azure just names things differently: service connections replace deploy keys, and environments gate production releases. Read the companion Azure DevOps YAML pipelines practical guide if YAML schema details trip you up.

How do you create your first Azure Pipelines YAML file?

Start inside your Azure DevOps project. Go to Pipelines → New pipeline, pick your repo, and choose YAML. Azure generates a starter file. Replace it with a multi-stage pipeline that separates build, test, and deploy concerns.

Save this as azure-pipelines.yml at the repository root. It targets a Laravel-style PHP app on PHP 8.3 with Composer 2.10:

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  phpVersion: '8.3'
  composerFlags: '--no-interaction --prefer-dist --optimize-autoloader'

stages:
  - stage: Build
    displayName: Build and test
    jobs:
      - job: BuildJob
        steps:
          - checkout: self
            fetchDepth: 1

          - script: |
              sudo update-alternatives --set php /usr/bin/php$(phpVersion)
              php -v
            displayName: Select PHP version

          - script: |
              curl -sS https://getcomposer.org/installer | php
              php composer.phar install $(composerFlags)
            displayName: Composer install

          - script: php artisan test
            displayName: Run PHPUnit
            env:
              APP_ENV: testing

          - task: PublishPipelineArtifact@1
            inputs:
              targetPath: $(System.DefaultWorkingDirectory)
              artifact: drop
              publishLocation: pipeline

  - stage: Deploy
    displayName: Deploy to production
    dependsOn: Build
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployProd
        environment: production
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: drop
                - task: SSH@0
                  inputs:
                    sshEndpoint: ProductionVps
                    runOptions: inline
                    inline: |
                      cd /var/www/myapp/current
                      php artisan migrate --force
                      php artisan config:cache

Commit and push. Azure DevOps detects the YAML file and offers to run the pipeline. The first run often fails on missing service connections—that is normal. Fix connections, re-run, and iterate.

Official reference for triggers, pools, and tasks lives in the Azure Pipelines YAML schema documentation. Bookmark it. You will open it weekly.

Understand the core YAML blocks

  • trigger — which branches start a run.
  • pool — which agent image executes steps.
  • stages / jobs / steps — the execution hierarchy.
  • variables — reusable values; mark secrets as secret variables in the UI.
  • environments — approval gates and deployment history for production.

How do you run tests and cache dependencies in Azure Pipelines?

Fast pipelines keep developers trusting CI. Slow ones get ignored. Cache Composer and npm directories so repeat runs skip redundant downloads—same principle as CI/CD caching for Composer and npm.

  - task: Cache@2
    inputs:
      key: 'composer | "$(Agent.OS)" | composer.lock'
      restoreKeys: |
        composer | "$(Agent.OS)"
      path: $(COMPOSER_CACHE_DIR)
    displayName: Cache Composer

  - script: |
      export COMPOSER_CACHE_DIR=$(Pipeline.Workspace)/.composer-cache
      php composer.phar install $(composerFlags)
    displayName: Composer install with cache

Add a Node build step when your app ships Vite 8.x assets:

  - task: NodeTool@0
    inputs:
      versionSpec: '26.x'
    displayName: Install Node.js 26 LTS

  - script: |
      npm ci
      npm run build
    displayName: Build front-end assets

Gate merges on tests, not on vanity metrics. A failing php artisan test should block deploy. Optional coverage gates belong in a later iteration—see code coverage gates in CI when you are ready.

Pipeline Stage FlowBuild StageComposer + npmPublish artifactTest StagePHPUnit suiteStatic analysisApprovalEnvironment gateManual checkDeploy StageSSH or App ServiceRun migrationsFailed test blocks all downstream stagescondition: succeeded() on each stageBranch filter limits prod deploys
Staged Azure Pipelines flow — build, test, optional approval, then deploy with branch conditions

How do you deploy from Azure Pipelines to a server or Azure App Service?

Deployment is where pipelines earn their keep. Pick one primary target per app. Mixing SSH tarball deploys with App Service zip deploy in the same pipeline creates confusion.

Option A: SSH deploy to Ubuntu VPS

Create an SSH service connection under Project settings → Service connections → New → SSH. Name it ProductionVps to match the YAML above. The SSH task runs remote shell commands on your Linux box—the same post-deploy steps I run after Deployer releases on sister legal-tech sites.

For symlink-based zero-downtime deploys, rsync a release folder over SSH, flip the current symlink, then reload PHP-FPM. Detailed VPS patterns appear in deploy a Laravel app with GitLab CI/CD to a VPS; swap GitLab job syntax for Azure tasks.

Option B: Azure App Service

Replace the SSH task with AzureWebApp@1 when hosting on App Service:

                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: MyAzureSubscription
                    appType: webAppLinux
                    appName: my-laravel-app
                    package: $(Pipeline.Workspace)/drop

App Service handles PHP runtime selection in the portal. Set WEBSITE_RUN_FROM_PACKAGE or deploy extracted files depending on your opcache strategy.

Option C: Azure Kubernetes Service

Container deploys belong in a separate pipeline stage. Follow deploy to AKS with Azure Pipelines once your Dockerfile is stable. Do not containerise on day one unless the team already runs Kubernetes in production.

Store database URLs, API keys, and SSH keys in Azure DevOps variable groups or Key Vault. Never commit them. The CI/CD secrets management best practices article covers rotation and least-privilege scopes that apply here too.

How does Azure Pipelines compare with Jenkins and GitLab CI?

Teams often ask which platform to standardise on. The answer depends on where your code and cloud budget already live—not feature checklists alone.

CriteriaAzure PipelinesGitLab CIJenkins
Hosting modelAzure DevOps SaaS + optional self-hosted agentsGitLab.com or self-managed GitLabSelf-hosted controller + agents you maintain
Config formatYAML in azure-pipelines.ymlYAML in .gitlab-ci.ymlJenkinsfile (Declarative or Scripted)
Free tier1 parallel job, 1,800 min/month on Microsoft-hosted agents400 CI minutes/month on GitLab.com freeFree software; you pay for servers
Azure integrationNative service connections, Key Vault, AKS, App ServiceWorks via tokens and scriptsPlugins; more wiring required
Best fitTeams on Azure or .NET + polyglot stacksSingle-platform git + CI (my default for Laravel VPS deploys)Maximum control, legacy estates, plugin ecosystems

For a deeper vendor shootout, read GitHub Actions vs GitLab CI in 2026. Azure Pipelines wins when Active Directory, Azure subscriptions, and compliance boundaries already centre on Microsoft. GitLab CI remains my go-to for Laravel on Ubuntu VPS with Deployer 7—see the Adventure Third Pole Trek booking platform portfolio entry for a Livewire + CI example.

CI Platform Trade-offsAzure PipelinesAzure nativeLow ops on SaaS agentsYAML in repo rootGitLab CIGit + CI unifiedStrong VPS deploy storyRunner minutes applyJenkinsFull self-host controlYou patch the controllerPlugin ecosystemChoose by existing stack, not hypeAlready on Azure → Azure PipelinesLaravel on VPS + Deployer → GitLab CILegacy Java/PHP farms → Jenkins
Azure Pipelines vs GitLab CI vs Jenkins — pick based on cloud footprint and who maintains agents

What mistakes break your first Azure Pipelines CI/CD setup?

Most first-pipeline failures are configuration, not code. These five issues appear on nearly every greenfield Azure DevOps project I review.

Wrong PHP version on the agent

Microsoft-hosted ubuntu-latest ships multiple PHP versions side by side. Your app may need 8.3 while the default binary is older. Always pin with update-alternatives or a setup script and print php -v in the log.

Missing environment approval on production

Without an environment: production block, every green build deploys immediately. Add approvers under Pipelines → Environments → production → Approvals. Blue/green patterns come later—start with a hard gate.

Secrets echoed in logs

Mark variables as secret in the UI. Never echo connection strings in bash steps. Azure masks secret variables automatically when referenced correctly.

Artifacts too large or wrong path

Publishing the entire repo including vendor/ bloats artifacts. Publish only deployable output, or rebuild on the deploy agent with a cached Composer lock. Validate artifact contents with the JSON formatter tool when debugging API manifest files—not pipeline logs themselves, but the same discipline applies.

Branch triggers firing on every feature branch deploy

Limit production deploys with branch conditions:

condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

Feature branches should run build and test only. Wire staging environments to develop if you use GitFlow.

First Pipeline GotchasSecrets in gitUse variable groups + Key VaultUnpinned PHPPrint php -v every buildNo prod approvalAdd environment gateFat artifactsExclude vendor if rebuiltFix checklist before go-liveBranch filter · secret vars · pinned runtimeTest rollback path on staging first
Common mistakes when you Azure Pipelines: Build Your First CI/CD Pipeline — secrets, PHP pinning, approvals, and artifacts

Solo developers and small Nepal teams should keep pipelines boring. One staging environment, one production gate, nightly backups on the server—that matches advice in CI/CD best practices for small teams. Need someone to wire this on your stack? Support and maintenance services and Linux system administration cover pipeline plus server hardening together.

Broader automation patterns live in build pipeline automation best practices. If you are new to the Azure DevOps UI itself, start with the Azure DevOps complete beginner guide before editing YAML.

Key Takeaways

  • Put azure-pipelines.yml in the repo root with separate build, test, and deploy stages.
  • Pin PHP 8.3+ and Composer 2.10 on agents; cache dependencies to keep runs under five minutes.
  • Store secrets in variable groups or Key Vault—never in committed YAML.
  • Gate production with an Azure DevOps environment approval and a main branch condition.
  • Pick one deploy target (SSH VPS, App Service, or AKS) and master it before adding complexity.
  • Compare Azure Pipelines with your existing CI; migrate incrementally rather than big-bang.

People Also Ask

Is Azure Pipelines free for small projects?

Yes. Azure DevOps includes free Microsoft-hosted parallel jobs with monthly minute quotas for private projects. Public repos receive more generous limits. Self-hosted agents are unlimited—you supply the VM. For a side project, the free tier is usually enough until you need multiple parallel jobs.

Can Azure Pipelines deploy to a non-Azure Linux server?

Yes. Create an SSH service connection and use the SSH task or copy files over SCP/rsync in a bash step. This is how most Laravel VPS deploys work. You do not need App Service or AKS unless you want managed hosting.

YAML or classic editor—which should beginners use?

Always YAML. Classic release pipelines still exist but Microsoft invests in YAML-first features—multi-stage pipelines, templates, and branch policies all assume code in git. Store pipeline changes in pull requests like application code.

How do Azure Pipelines fit with Laravel 13?

Laravel 13.x requires PHP 8.3 minimum. Select that version on the agent, run composer install, php artisan test, then deploy. Queue workers and schedulers remain server-side cron jobs after deploy—they are not pipeline steps unless you explicitly restart systemd units over SSH.

Ship your first pipeline this week

Azure Pipelines: Build Your First CI/CD Pipeline boils down to one YAML file, one service connection, and one successful green run. Start with build plus test only. Add deploy after tests pass three times in a row. Branch from the Jenkins or GitLab examples in build a CI/CD pipeline with Jenkins if you are migrating—then delete duplicate steps until Azure owns the path to production.

When you want hands-on help wiring Azure DevOps to a Laravel app, VPS, or App Service—or comparing CI options for a Nepal startup budget—custom software development and enterprise application development cover architecture through deploy. Read blue/green deployment explained before your second pipeline iteration. About me lists the stacks I ship daily.

Contact us with your repo layout and deploy target. We will sketch a pipeline YAML you can paste into Azure DevOps and run the same day.

Frequently Asked Questions

Azure Pipelines is the CI/CD service inside Azure DevOps. You version-control build, test, and deploy steps in an azure-pipelines.yml file at your repo root. A git push triggers the pipeline on Microsoft-hosted or self-hosted agents. Stages run jobs that checkout code, install dependencies, run tests, publish artifacts, and deploy to targets like an Ubuntu VPS, Azure App Service, or AKS. If you already use GitLab CI or Jenkins, the same concepts apply—triggers, agents, artifacts, and environments—under Azure DevOps naming.

Yes. Azure DevOps includes free Microsoft-hosted parallel jobs with monthly minute quotas on private projects, and public repos receive more generous limits. Self-hosted agents are unlimited—you supply the VM.

Gather four things before writing YAML: source control in Azure Repos, GitHub, or Bitbucket; a deploy target such as an Ubuntu VPS over SSH, Azure App Service, or AKS; secrets stored in pipeline variables or Azure Key Vault, never in git; and a clear branch strategy. You also need an Azure DevOps organisation at dev.azure.com, a repository with a runnable test suite, and an agent pool—microsoft-hosted ubuntu-latest works for most PHP builds on Laravel 12 or 13.x with PHP 8.3+ and Composer 2.10.

Inside your Azure DevOps project, go to Pipelines, then New pipeline, select your repository, and choose YAML. Azure generates a starter file—replace it with a multi-stage pipeline separating build, test, and deploy. Save it as azure-pipelines.yml at the repository root. Define a trigger on main, set pool vmImage to ubuntu-latest, add build steps for PHP selection, Composer install, and php artisan test, then a Deploy stage with an environment block and your target task. Commit, push, and Azure DevOps offers to run the pipeline on detection.

Pin PHP 8.3 on the agent with update-alternatives, install dependencies via Composer 2.10, set APP_ENV to testing, and run php artisan test as a build-stage step. A failing test should block deploy—gate merges on tests, not vanity metrics. Optional coverage gates belong in a later iteration. On Laravel 13.x, PHP 8.3 is the minimum. Laravel 12 still runs on PHP 8.2, but the article recommends pinning 8.3+ for consistency across agent images that ship multiple PHP versions side by side.

Add a Cache@2 task keyed on composer.lock and Agent.OS, pointing path to COMPOSER_CACHE_DIR under Pipeline.Workspace. Export that directory before composer install so repeat runs skip redundant downloads—the same principle as CI/CD caching elsewhere. When your app compiles front-end assets with Vite 8.x, add NodeTool@0 with versionSpec 26.x, then run npm ci and npm run build. Fast pipelines keep developers trusting CI; slow ones get ignored. Target repeat runs under five minutes with caching enabled.

Yes. Create an SSH service connection under Project settings, name it to match your YAML sshEndpoint, and use the SSH@0 task with inline remote shell commands. This is how most Laravel VPS deploys work—you do not need App Service or AKS unless you want managed hosting. For zero-downtime deploys, rsync a release folder over SSH, flip the current symlink, then reload PHP-FPM—the same post-deploy pattern used after Deployer 7 releases on production Ubuntu servers.

Replace the SSH task with AzureWebApp@1 in your Deploy stage. Set azureSubscription to your service connection name, appType to webAppLinux, appName to your App Service, and package to the published artifact path such as Pipeline.Workspace/drop. App Service handles PHP runtime selection in the portal. Choose WEBSITE_RUN_FROM_PACKAGE or deploy extracted files depending on your opcache strategy. Pick one primary deploy target per app—mixing SSH tarball deploys with App Service zip deploy in the same pipeline creates confusion.

The choice depends on where your code and cloud budget already live. Azure Pipelines offers native Azure integration via service connections, Key Vault, AKS, and App Service, with YAML in azure-pipelines.yml and a free tier of one parallel job and 1,800 minutes per month on Microsoft-hosted agents. GitLab CI gives 400 CI minutes per month on GitLab.com free and suits single-platform git plus CI—my default for Laravel VPS deploys with Deployer 7. Jenkins is free software but you maintain the controller and agents. Azure wins when Active Directory and Azure subscriptions already centre on Microsoft.

Five issues appear on nearly every greenfield project. Wrong PHP version—the default agent binary may be older than your app needs, so always pin with update-alternatives and print php -v. Missing environment approval lets every green build deploy immediately—add approvers under Environments, production, Approvals. Secrets echoed in logs—mark variables as secret and never echo connection strings. Artifacts too large from publishing vendor/—publish only deployable output. Branch triggers firing on feature branches—limit production with a main branch condition while feature branches run build and test only.

Always YAML. Classic release pipelines still exist, but Microsoft invests in YAML-first features—multi-stage pipelines, templates, and branch policies all assume pipeline code lives in git.

Add an environment block named production in your Deploy stage deployment job. Under Pipelines, Environments, production, configure Approvals so a human must sign off before release. Combine this with a branch condition on the Deploy stage: condition and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')). Feature branches should run build and test only. Wire a staging environment to develop if you use GitFlow. Solo developers and small teams should keep it boring—one staging environment, one production gate—before exploring blue/green patterns.

Store database URLs, API keys, and SSH keys in Azure DevOps variable groups or Azure Key Vault—never commit them to azure-pipelines.yml. Mark sensitive values as secret in the UI; Azure masks them automatically when referenced correctly in steps. External repo access uses service connections rather than raw credentials in YAML. The same least-privilege and rotation principles from general CI/CD secrets management apply here. On first runs, missing service connections cause failures—that is normal until you wire connections and re-run.

Laravel 13.x requires PHP 8.3 minimum. In your build stage, select that version on the ubuntu-latest agent, run composer install with Composer 2.10, execute php artisan test, publish artifacts, then deploy via SSH or App Service. Queue workers and schedulers remain server-side cron jobs after deploy—they are not pipeline steps unless you explicitly restart systemd units over SSH. Optionally add Node.js 26 LTS and npm 12 to compile Vite 8.x assets before publishing the artifact.

Treat the first failure as normal—missing service connections are the most common cause. Create the SSH or Azure subscription service connection referenced in your YAML, verify secret variables are set and marked secret, confirm PHP pinning with php -v in the log, then re-run and iterate. Start with build plus test only; add deploy after tests pass three times in a row. If YAML schema details trip you up, consult the official Azure Pipelines YAML schema documentation and the companion Azure DevOps YAML practical guide before adding complexity like AKS container stages.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: