Reading time: 14 min read

Managing Vercel Deployments with Azure DevOps Release Pipelines

A Guide to artifact-based deployments with multi-environment promotion

Portrait photo of Adam Borsi, article author

A more controlled approach to Vercel deployments

This guide expands on our previous article Deploying from Azure DevOps to Vercel: A Comprehensive Guide by introducing a release pipeline approach that gives you greater control over your Vercel deployments from Azure DevOps. Instead of triggering deployments directly from your build pipeline, this method creates redistributable deployment artifacts that can be promoted through multiple environments (development, staging, production) using Azure DevOps Release Pipelines.

Why use release pipelines?

Traditional approach limitations:

  • Build pipelines deploy directly to Vercel immediately after code builds
  • No ability to test artifacts before production deployment
  • Difficult to coordinate releases across multiple environments
  • Cannot easily rollback to previous versions
  • Limited approval workflows and deployment gates

Release pipeline benefits:

  • Artifact promotion: Build once, deploy the same artifact to multiple environments
  • Deployment control: Manual or automated releases with approval gates
  • Environment management: Separate configurations for dev, staging, and production
  • Rollback capability: Easily redeploy previous release artifacts
  • Audit trail: Track who deployed what and when
  • Coordinated releases: Deploy alongside other services in your release orchestration

Prerequisites

Before starting this guide, ensure you have:

Azure DevOps

  • Project with appropriate permissions to:
    • Create and edit build pipelines
    • Create and manage release pipelines
    • Enable classic pipeline features (organization or project-level admin access may be required)
  • Existing repository containing your Vercel application code

Vercel

  • Active Vercel account (personal or team)
  • Existing Vercel project configured for your application
  • Vercel API token with deployment permissions (generate here)
  • Team scope identifier (if using Vercel Teams)

Your Project

  • Application code compatible with Vercel deployment
  • Knowledge of your project's source directory structure
  • .vercel configuration directories for each target environment (e.g., .vercel-dev, .vercel-staging, .vercel-production)

Technical knowledge

  • Basic understanding of Azure DevOps pipelines (YAML and Classic)
  • Familiarity with PowerShell scripting
  • Understanding of CI/CD concepts

What you'll accomplish

By the end of this guide, you will have:

  1. A build pipeline that packages your Vercel application code into a deployment artifact (zip file)
  2. A PowerShell deployment script that handles Vercel CLI operations and environment-specific deployments
  3. A release pipeline with multiple stages (dev, staging, production) that can deploy your artifact to different Vercel environments
  4. Environment-specific configurations allowing you to control deployment targets, tokens, and project settings per stage
  5. A repeatable release process that enables you to promote the same tested artifact through your deployment pipeline

End result: You'll be able to trigger a build, create a release artifact, and promote that artifact through your environments with proper controls and approvals—integrating Vercel deployments into your broader CI/CD workflow.

Implementation steps

Step 1: Enable classic build pipelines

Support for classic build pipelines is needed and must be enabled. This can be done at the project level, but if these options are greyed out (disabled) and locked down, then you will need to do it at the account level under Pipelines → Settings.

Step 2: Establish the build pipeline.

Start by creating the YAML file vercel-artifact.yml for import into your repository.

trigger:
- none
pool:
  vmImage: 'ubuntu-latest'
variables:
  buildConfiguration: 'Release'
  targetDirectory: '$(Build.SourcesDirectory)/src/Rendering'
  artifactName: 'deployment-package'
  zipFileName: 'deployment.zip'
stages:
- stage: BuildAndPackage
  displayName: 'Build and Package'
  jobs:
  - job: CreateArtifact
    displayName: 'Create Deployment Artifact'
    steps:
    
    # Checkout source code
    - checkout: self
      displayName: 'Checkout Repository'
    
    # Validate target directory exists
    - task: PowerShell@2
      displayName: 'Validate Target Directory'
      inputs:
        targetType: 'inline'
        script: |
          $targetDir = "$(targetDirectory)"
          Write-Host "Checking if target directory exists: $targetDir"
          
          if (Test-Path $targetDir) {
            Write-Host "✓ Target directory found: $targetDir"
            $itemCount = (Get-ChildItem -Path $targetDir -Recurse | Measure-Object).Count
            Write-Host "Directory contains $itemCount items"
          } else {
            Write-Error "✗ Target directory not found: $targetDir"
            exit 1
          }
    
    # Copy vercel-release.ps1 to artifact staging directory
    - task: PowerShell@2
      displayName: 'Copy Vercel Release Script'
      inputs:
        targetType: 'inline'
        script: |
          $sourceFile = "$(Build.SourcesDirectory)/vercel-deploy-temp/vercel-release.ps1"
          $destinationDir = "$(Build.ArtifactStagingDirectory)"
          $destinationFile = "$destinationDir/vercel-release.ps1"
          
          Write-Host "Source file: $sourceFile"
          Write-Host "Destination: $destinationFile"
          
          if (Test-Path $sourceFile) {
            Write-Host "✓ Found vercel-release.ps1 script"
            Copy-Item -Path $sourceFile -Destination $destinationFile -Force
            Write-Host "✓ Successfully copied vercel-release.ps1 to artifact staging directory"
          } else {
            Write-Warning "⚠ vercel-release.ps1 not found at: $sourceFile"
            Write-Host "Pipeline will continue without the script"
          }
    
    # Copy vercel-release.ps1 to artifact staging directory
    - task: PowerShell@2
      displayName: 'Copy Vercel Promote Script'
      inputs:
        targetType: 'inline'
        script: |
          $sourceFile = "$(Build.SourcesDirectory)/vercel-deploy-temp/vercel-promote.ps1"
          $destinationDir = "$(Build.ArtifactStagingDirectory)"
          $destinationFile = "$destinationDir/vercel-promote.ps1"
          
          Write-Host "Source file: $sourceFile"
          Write-Host "Destination: $destinationFile"
          
          if (Test-Path $sourceFile) {
            Write-Host "✓ Found vercel-promote.ps1 script"
            Copy-Item -Path $sourceFile -Destination $destinationFile -Force
            Write-Host "✓ Successfully copied vercel-promote.ps1 to artifact staging directory"
          } else {
            Write-Warning "⚠ vercel-promote.ps1 not found at: $sourceFile"
            Write-Host "Pipeline will continue without the script"
          }
    # Create zip archive
    - task: ArchiveFiles@2
      displayName: 'Create Zip Archive'
      inputs:
        rootFolderOrFile: '$(targetDirectory)'
        includeRootFolder: false
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/$(zipFileName)'
        replaceExistingArchive: true
        verbose: true
    
    # Display artifact information
    - task: PowerShell@2
      displayName: 'Display Artifact Information'
      inputs:
        targetType: 'inline'
        script: |
          $zipPath = "$(Build.ArtifactStagingDirectory)/$(zipFileName)"
          $scriptReleasePath = "$(Build.ArtifactStagingDirectory)/vercel-release.ps1"
          $scriptPromotePath = "$(Build.ArtifactStagingDirectory)/vercel-promote.ps1"
          
          # Check zip file
          if (Test-Path $zipPath) {
            $fileInfo = Get-Item $zipPath
            $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)
            Write-Host "✓ Zip file created successfully"
            Write-Host "File: $($fileInfo.Name)"
            Write-Host "Size: $fileSizeMB MB"
            Write-Host "Path: $($fileInfo.FullName)"
          } else {
            Write-Error "✗ Zip file was not created"
            exit 1
          }
          
          # Check release script file
          if (Test-Path $scriptReleasePath) {
            $scriptInfo = Get-Item $scriptReleasePath
            Write-Host "✓ Vercel release script included"
            Write-Host "Script: $($scriptInfo.Name)"
            Write-Host "Script Path: $($scriptInfo.FullName)"
          } else {
            Write-Host "ℹ Vercel release script not included in artifacts"
          }
          # Check promote script file
          if (Test-Path $scriptPromotePath) {
            $scriptInfo = Get-Item $scriptPromotePath
            Write-Host "✓ Vercel promote script included"
            Write-Host "Script: $($scriptInfo.Name)"
            Write-Host "Script Path: $($scriptInfo.FullName)"
          } else {
            Write-Host "ℹ Vercel promote script not included in artifacts"
          }
    
    # Publish zip artifact
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Zip Artifact'
      inputs:
        pathToPublish: '$(Build.ArtifactStagingDirectory)/$(zipFileName)'
        artifactName: '$(artifactName)'
        publishLocation: 'Container'
    
    # Publish vercel release script as separate artifact (if exists)
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Vercel Release Script'
      condition: succeeded()
      continueOnError: true
      inputs:
        pathToPublish: '$(Build.ArtifactStagingDirectory)/vercel-release.ps1'
        artifactName: '$(artifactName)'
        publishLocation: 'Container'
    
    # Publish vercel promote script as separate artifact (if exists)
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Vercel Promote Script'
      condition: succeeded()
      continueOnError: true
      inputs:
        pathToPublish: '$(Build.ArtifactStagingDirectory)/vercel-promote.ps1'
        artifactName: '$(artifactName)'
        publishLocation: 'Container'
    # Summary
    - task: PowerShell@2
      displayName: 'Pipeline Summary'
      inputs:
        targetType: 'inline'
        script: |
          Write-Host "==================== PIPELINE SUMMARY ===================="
          Write-Host "Source Directory: $(targetDirectory)"
          Write-Host "Zip File: $(zipFileName)"
          Write-Host "Artifact Name: $(artifactName)"
          Write-Host "Artifact Location: $(Build.ArtifactStagingDirectory)"
          Write-Host "Build ID: $(Build.BuildId)"
          Write-Host "Build Number: $(Build.BuildNumber)"
          
          # Check what artifacts were created
          $zipExists = Test-Path "$(Build.ArtifactStagingDirectory)/$(zipFileName)"
          $scriptReleaseExists = Test-Path "$(Build.ArtifactStagingDirectory)/vercel-release.ps1"
          $scriptPromoteExists = Test-Path "$(Build.ArtifactStagingDirectory)/vercel-promote.ps1"
          
          Write-Host ""
          Write-Host "Artifacts Created:"
          Write-Host "- Zip Package: $(if($zipExists){'✓ Yes'}else{'✗ No'})"
          Write-Host "- Vercel Release Script: $(if($scriptReleaseExists){'✓ Yes'}else{'✗ No'})"
          Write-Host "- Vercel Promote Script: $(if($scriptPromoteExists){'✓ Yes'}else{'✗ No'})"
          Write-Host "========================================================"

Key Configuration:

  • The Trigger This is set to trigger of none, so it must be manually executed. This can be easily modified if your implementation requires more automation.
  • Variables targetDirectory This should be set to where your Vercel Head code resides. '\$(Build.SourcesDirectory)/' is typically the root dir of the solution/repo. You can set a sub directory after the slash if necessary. This is common when your headless code is shared in a more “monolithic repository" structure.
  • artifactName This can be altered to fit an organizational format. This is the “directory” the zip file will be held in.
  • zipFileName Once more, this can be altered to reflect an organizational format.

Step 3: Create the release script

The release script is where the Vercel CLI commands live that will perform the specific deployment. Create a new file named vercel-release.ps1 in your repository at the path vercel-deploy-temp/vercel-release.ps1. This path can be modified but ensure it matches the referenced path in Step 2’s build pipeline.

Add the following PowerShell code:

# PowerShell Script to deploy via Vercel
           
Write-Host "Starting vercel-release.ps1"
# Define the specific zip file to look for
$zipFileName = "deployment.zip"
$zipPath = "./$zipFileName"
Write-Host "Looking for deployment package: $zipFileName @ $zipPath"
if (Test-Path $zipPath) {
    Write-Host "Zip found and extracting: $zipFileName" -ForegroundColor Green
    
    # Extract the zip file
    Expand-Archive -Path $zipPath -DestinationPath "./extracted" -Force
    
    # Change to the extracted directory
    Set-Location "./extracted"
    
    Write-Host "Contents after extraction:"
    Get-ChildItem -Recurse | Format-Table Name, Length, LastWriteTime
    
	if ($env:projectTarget) {
		Write-Host "Found Target: $env:projectTarget" -ForegroundColor Green
		
		# Rename .vercel-* to .vercel if it exists
		if (Test-Path ".vercel-$env:projectTarget") {
			Write-Host "Renaming .vercel-$env:projectTarget to .vercel" -ForegroundColor Yellow
			Rename-Item -Path ".vercel-$env:projectTarget" -NewName ".vercel"
		
		
			Write-Host "Running Vercel deployment process..." -ForegroundColor Green
			
			if ($env:VercelToken -AND $env:VercelScope -AND $env:VercelSlot) {
				Write-Host "Found Token: $($env:VercelToken.Substring(0,8))..." -ForegroundColor Green
				Write-Host "Found Scope: $env:VercelScope" -ForegroundColor Green
				Write-Host "Found Slot: $env:VercelSlot" -ForegroundColor Green
				
				Write-Host "Installing Vercel CLI" -ForegroundColor Green
				npm install --global vercel@33.5.5
				
				if ($LASTEXITCODE -eq 0) {
					Write-Host "Vercel CLI installed successfully" -ForegroundColor Green
					
					Write-Host "Pulling environment variables" -ForegroundColor Green
					vercel env pull --yes --environment=$env:VercelSlot --token $env:VercelToken --scope $env:VercelScope
					
					if ($LASTEXITCODE -eq 0) {
						Write-Host "Environment variables pulled successfully" -ForegroundColor Green
						
						Write-Host "Deploying to Vercel with target: $env:VercelSlot" -ForegroundColor Green
						
						# Use -prod flag if the slot is "production", otherwise it deploys to Preview by default
						$prodFlag = ""
						if ($env:VercelSlot -eq "production") {
						    $prodFlag = "--prod"
						    Write-Host "Using --prod flag for Production deployment." -ForegroundColor Green
						} else {
						    Write-Host "Deploying to Preview/Development (default)." -ForegroundColor Yellow
						}
						# The deployment command
						vercel $prodFlag --token $env:VercelToken --scope $env:VercelScope
						
						if ($LASTEXITCODE -eq 0) {
							Write-Host "Deployment completed successfully!" -ForegroundColor Green
						} else {
							Write-Error "Vercel deployment failed with exit code: $LASTEXITCODE"
							exit 1
						}
					} else {
						Write-Error "Failed to pull environment variables. Exit code: $LASTEXITCODE"
						exit 1
					}
				} else {
					Write-Error "Failed to install Vercel CLI. Exit code: $LASTEXITCODE"
					exit 1
				}
			} else {
				Write-Error "VercelToken or VercelScope or VercelSlot environment variables are not defined"
				Write-Host "Please ensure these environment variables are set:"
				Write-Host "- VercelToken: Your Vercel authentication token"
				Write-Host "- VercelScope: Your Vercel team/scope identifier"
				Write-Host "- VercelSlot: Your Vercel slot identifier"
				exit 1
			}
		} else {
			Write-Error ".vercel-$env:projectTarget directory not found"
			Write-Host "Please ensure these environment variables are set:"
			Write-Host "- projectTarget: match the directory target for Vercel settings"
			exit 1
		}
	} else {
		Write-Error "projectTarget environment variables are not defined"
		Write-Host "Please ensure these environment variables are set:"
		Write-Host "- projectTarget: match the directory target for Vercel settings"
		exit 1
	}
} else {
    Write-Error "Deployment package not found: $zipPath"
    Write-Host "Available files in current directory:"
    Get-ChildItem | Format-Table Name, Length, LastWriteTime
    exit 1
}

The deployment execution happens in the Vercel CLI command (around line 63 of the script): vercel $prodFlag --token $env:VercelToken --scope $env:VercelScope

This command:

  • Authenticates using your VercelToken
  • Targets your specific team/account via VercelScope
  • Deploys to the environment specified in \$prodFlag (production or preview), as defined by VercelSlot environment variable

The variables referenced within this PowerShell Script are all setup in the Release pipeline and will be covered within Step 4.5 Set the properties of each Stage.

Step 3.1: Quickly Review – Vercel Project Configuration

In our previous article we go into detail how to setup the Vercel Project Configuration files and directories, the details that make them up and why we use them.

Here is a direct link to that portion of the article.

If you’re building off the solution from the previous article, this review process is not needed.

Step 4: Creating a new release pipeline

The setting up of a new Release Pipeline. We’ll start in the Pipelines → Releases section, which should now be enabled with the settings we turned on at the beginning of the implementation steps.

Step 4.1 Create a New Release Pipeline

Start by clicking the “+ New” button and select the “+ New release pipeline” option.

You will be presented with the following or similar screen as below:

This screen sets off the process to setup the initial Stage of the new release pipeline. Right at the top of the “Select Template” modal, choose “Or start with an Empty Job”.

Step 4.2 Setting the Stage

The first settings presented here are to set the name of the Stage.

This Stage should be named after the target Environment within the Vercel Project. Once the Stage name is set click the Save button in the header. Typically, you will be adding 2-3 Stages to cover the common uses.

Step 4.3 Rename your Release Pipeline

We can also rename the Release Pipeline by selecting the title in the header and clicking the Save button to the right of the field.

Step 4.4 Add an Artifact

Next we’ll move onto attaching an Artifact to this Release Pipeline.

Click the Add an artifact button to get started.

This will bring up the “Add an artifact” wizard to the right of the screen. In this example, it is focused on ADO Builds to Release Pipeline only, there are many other options to pull in artifacts into this process.

The source type should be set to “Build”, the Project set to the Target project. In this case the same project this pipeline is being created, and the Source should be the Pipeline that creates the build artifact.

Once it is chosen the wizard will update:

Click Add to proceed.

Key Configuration:

  • Project Name of the project that contains the build pipeline
  • Source (build pipline) Name or ID of the build pipeline that publishes the artifact
  • Default Version The default version will be deployed when new releases are created. The version can be changed for manually created releases at the time of release creation
  • Source alias This is an identifier (typically a short name) that uniquely identifies an artifact linked to a release pipeline. It cannot contain the characters: \ / : " ? < > or |

Step 4.5 Set the properties of each Stage

Before we setup each stage in the Release Pipeline, we’ll set some “global” variables to the Pipeline itself. Because these values are utilized by each of the Stages, there isn’t a need to place them on the individual Stages which may create an update headache down the line if any of these values require an update.

Start under the Pipeline Variables, and add two new items to the list:

  • VercelScope your-team-name (found in Vercel dashboard URL: vercel.com/your-team-name)
  • VercelToken (API token provided to you by Vercel) To generate go to your Account Settings and create a new Token. Note, all actions will be bound to the account that issues the token in the logs.
  • Scope for each should be set to Release.

With all the pieces in place, we now set the properties on each deployment stage and you’re ready to manage your releases.

Repeat these steps for each stage you have set up for your Release Pipeline.

You will see a link/summary on the stage, likely 1 job, 0 task. Click it to initiate the next steps.

This will bring you into the Stage → Task settings.

You will see “Agent Job” and a “+” sign. Click that to add a task.

Under Add tasks, search for “Powershell” and add the first one.

Select it to fill out the properties.

Type should be File Path, and you have to set the Script Path. Click the ellipsis button.

This will bring up a modal from the artifact folder structure.

Select the vercel-release.ps1 file and click OK.

Next expand “Advanced” and fill out the Working Directory.

Click the ellipsis to choose a directory.

And click OK.

Next expand Environment Variables, this is where you’ll set the general Vercel variables to point to your team and the API token to access it. You will also define the project target (referencing the directory with the project specific settings, eg. dev-vercel), and set the deployment slot for vercel.

Add the following Environment Variables:

  • projectTarget dev (value must match your .vercel-dev folder name without the .vercel- prefix for each target configuration)
  • VercelSlot preview (options: production or preview - maps to Vercel deployment environments, Production will be available immediately, and Preview will allow you to test out the content internally before you promote it to Production via Vercel)

Click the Save button. Repeat the step 4.5 for any other Stages in the pipeline.

Step 4.6 Run it!

Once that is completed, you’re ready to manage your releases.

Click Create release to start the process.

Testing your setup

Before deploying to production, verify your configuration:

  1. Run the Build Pipeline
    • Navigate to Pipelines → Builds
    • Select your vercel-artifact.yml pipeline
    • Click "Run pipeline"
    • Verify the artifact is created successfully (check for the zip file and PowerShell script)
  2. Create a Test Release
    • Go to Pipelines → Releases
    • Click "Create release"
    • Select your development stage
    • Monitor the release execution logs
    • Verify deployment success in your Vercel dashboard
  3. Verify Deployment
    • Check the Vercel dashboard for your deployment URL
    • Test the deployed application
    • Review deployment logs in both Azure DevOps and Vercel

Next steps

Now that your release pipeline is configured, consider these enhancements:

  • Add Approval Gates Configure pre-deployment approvals for staging and production stages to prevent accidental deployments. Go to your Stage settings → Pre-deployment conditions → Pre-deployment approvals.
  • Automate Release Creation Enable continuous deployment triggers on your artifact to automatically create releases when new builds complete. Click the lightning bolt icon on your artifact in the release pipeline.
  • Add Post-Deployment Testing Configure post-deployment gates to run automated tests after each stage deployment before promoting to the next environment.
  • Set Up Notifications Configure email or Slack notifications for deployment successes and failures under Project Settings → Notifications.

Troubleshooting common issues

"Deployment package not found" error

  • Verify the artifact name in your release pipeline matches the artifactName variable in your build pipeline
  • Check that the build pipeline completed successfully and published artifacts
  • Ensure the working directory path in your PowerShell task is correct

"Vercel CLI installation failed" error

  • The build agent may have network restrictions
  • Try using a different agent pool or contact your Azure DevOps administrator

".vercel-\[target\] directory not found" error

  • Confirm your .vercel-dev, .vercel-staging, etc. directories exist in your repository
  • Verify the projectTarget environment variable matches your directory naming (without the .vercel- prefix)
  • Review Step 3.1 and the linked article for proper Vercel project configuration

Authentication errors

  • Regenerate your Vercel API token and update the VercelToken environment variable
  • Verify your VercelScope exactly matches your team name in Vercel (case-sensitive)
  • Ensure the token has deployment permissions

Wrong environment deployed

  • Double-check the VercelSlot environment variable for each stage
  • Verify your .vercel-\ directories contain the correct project configuration

Additional Resources

Summary

You now have a robust deployment pipeline that:

  • Builds your Vercel application into reusable artifacts
  • Promotes the same tested artifact across multiple environments
  • Provides control, auditability, and rollback capabilities
  • Integrates Vercel deployments into your existing Azure DevOps workflow

This approach gives your team the confidence to deploy frequently while maintaining proper controls and quality gates throughout your release process.