10.2 Binary, Web App, Function & Scripted Deployments
Key Takeaways
- Deploying web applications and Azure Functions using 'WEBSITE_RUN_FROM_PACKAGE=1' mounts an immutable zip archive directly as the read-only wwwroot directory, completely eliminating file locks and speeding up cold starts.
- YAML pipelines manage IaaS virtual machine deployments using Environment VM resources and rolling batch strategies, superseding legacy Classic Release Pipeline Deployment Groups.
- The Azure Pipelines agent installed on target VMs registers to Environment pools, leveraging custom tags (e.g., 'role: web') to direct rolling script execution across defined server batches.
- Scripted post-deployment automation using AzureCLI@2 and AzurePowerShell@5 must enforce cross-platform runtime execution ('pwsh: true') and strict failure trapping ('failOnStandardError: true').
- Deployment scripts must be strictly designed for idempotency, ensuring repeated pipeline runs achieve the identical desired end state without creating duplicate resources or corrupting configurations.
10.2 Binary, Web App, Function & Scripted Deployments
While containerization dominates cloud-native architectures, enterprise delivery pipelines frequently manage compiled binary artifacts, Platform-as-a-Service (PaaS) web workloads, serverless functions, and traditional Infrastructure-as-a-Service (IaaS) virtual machines. Achieving zero downtime across these diverse hosting models requires specialized deployment mechanics: eliminating runtime file locks on compiled assemblies, coordinating rolling script updates across virtual machine fleets, and constructing idempotent automation scripts.
For the AZ-400 exam, candidates must master the internal mechanics of Zip Deploy and Run From Package, transition legacy Deployment Groups into modern YAML Environment VM resources, and author resilient, cross-platform deployment automation scripts using Azure CLI and PowerShell Core.
1. Deploying Web Apps & Azure Functions: Zip Deploy vs. Run From Package
Deploying application code to Azure App Service and Azure Functions has evolved through several technical paradigms. Early methods—such as FTP, WebDeploy (MSDeploy), and continuous Kudu git pushes—relied on copying individual files into the web root directory (/home/site/wwwroot on Linux or D:\home\site\wwwroot on Windows). In enterprise production environments, this file-by-file copy introduces severe failure vectors:
- File Locking (
DLL in use): If an active worker process has compiled binaries or native assemblies open in memory, overwriting them during deployment generates anERROR_FILE_IN_USEorERROR_ACCESS_DENIEDexception, causing corrupted deployments. - Inconsistent Intermediate State: During a prolonged file copy, client requests hitting the server execute a hybrid mix of old and new assemblies, resulting in runtime crashes.
- Deployment Leftovers: Deleting obsolete files requires manual synchronization. Obsolete assemblies left behind can introduce security vulnerabilities or unintended dependency resolution.
The Solution: Run From Package (WEBSITE_RUN_FROM_PACKAGE=1)
To resolve these challenges, Microsoft introduced Run From Package, enabled by setting the application setting WEBSITE_RUN_FROM_PACKAGE = 1 (or setting it to a secure Azure Blob Storage shared access signature URL).
┌────────────────────────────────────────────────────────────────────────┐
│ Azure App Service / Function App │
│ │
│ [Application Setting: WEBSITE_RUN_FROM_PACKAGE = 1] │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Storage / SitePackages: │ │
│ │ 20260905-142010.zip (Immutable Package Archive) │ │
│ └─────────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ (Mounted as Virtual Read-Only FS) │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ /home/site/wwwroot (or D:\home\site\wwwroot) │ │
│ │ • Read-Only File System │ │
│ │ • Zero File Locks / Atomic Pointer Swap │ │
│ │ • Pre-Indexed for Ultra-Fast JIT Assembly Loading │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
Architectural Mechanics & Benefits
- Atomic Mount Operation: Instead of extracting thousands of individual files onto the local disk, Azure mounts the zip file directly as the virtual
wwwrootdirectory. The deployment completes as an atomic pointer swap. - Elimination of File Locks: Because the mounted filesystem is strictly read-only, worker processes cannot lock files in a way that blocks subsequent deployments. A new zip file is uploaded alongside the old one, and Azure updates the mount point seamlessly.
- Drastic Cold Start Reduction: In serverless Azure Functions and auto-scaled App Services, loading hundreds of loose files across remote storage shares causes substantial disk I/O latency. Reading from an indexed, immutable package eliminates file traversal latency, accelerating cold start initialization by up to 50%.
- Complete Artifact Integrity: The application running in production is guaranteed to match the exact byte-for-byte binary package generated by the CI build pipeline, preventing configuration drift.
[!CAUTION] Because
wwwrootbecomes strictly read-only underWEBSITE_RUN_FROM_PACKAGE=1, any legacy code that attempts to write log files, cache data, or upload temp files directly intowwwrootwill throw runtimeUnauthorizedAccessExceptionorIOExceptionerrors. All dynamic runtime writes must be directed to%TEMP%or/tmp.
Pipeline Implementation: AzureWebApp@1 and AzureFunctionApp@1
# Deploying an ASP.NET Core Web App using Run-From-Package
- task: AzureWebApp@1
displayName: 'Deploy Web App to Production'
inputs:
azureSubscription: 'ContosoARMConnection'
appType: 'webApp'
appName: 'app-contoso-core'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
deploymentMethod: 'runFromPackage'
# Deploying an Azure Function App
- task: AzureFunctionApp@1
displayName: 'Deploy Order Processing Function'
inputs:
azureSubscription: 'ContosoARMConnection'
appType: 'functionApp'
appName: 'func-order-processor'
package: '$(Pipeline.Workspace)/drop/func-order.zip'
deploymentMethod: 'runFromPackage'
2. Virtual Machine Deployments: Deployment Groups vs. Environment VM Resources
Enterprise architectures frequently maintain persistent Infrastructure-as-a-Service (IaaS) virtual machines running Linux (RHEL, Ubuntu) or Windows Server for legacy frameworks, specialized third-party software, or high-compliance workloads.
Evolution from Classic Deployment Groups to YAML Environments
- Classic Release Pipelines (Deployment Groups): In the legacy Classic UI, administrators provisioned a Deployment Group within the Azure DevOps project. Running a registration script on each VM installed the Azure Pipelines agent and registered the machine into the group with metadata tags (e.g.,
web,db,eastus). While functional, Classic Deployment Groups lack Git versioning, pull request validation, and branch isolation. - Modern YAML Pipelines (Environments & VM Resources): In YAML pipelines, deployment targets are represented as Environments. An Environment can contain Kubernetes clusters or Virtual Machine resources. The Azure Pipelines agent installed on the VM registers directly to the environment, enabling true Configuration-as-Code delivery.
┌───────────────────────────────────────────────────────────────────────┐
│ Azure DevOps YAML Pipeline │
│ Environment: 'production-vms' │
└───────────────────────────────────┬───────────────────────────────────┘
│ (Rolling Strategy: maxParallel: 2)
┌─────────────────────────────┴─────────────────────────────┐
▼ ▼
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ VM Resource: 'vm-web-01' │ │ VM Resource: 'vm-web-02' │
│ • Agent Pool: production-vms │ │ • Agent Pool: production-vms │
│ • Tags: [role: web, zone: A] │ │ • Tags: [role: web, zone: B] │
│ • Status: Updating Batch 1 │ │ • Status: Updating Batch 1 │
└───────────────────────────────┘ └───────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ VM Resource: 'vm-web-03' │ │ VM Resource: 'vm-web-04' │
│ • Agent Pool: production-vms │ │ • Agent Pool: production-vms │
│ • Tags: [role: web, zone: A] │ │ • Tags: [role: web, zone: B] │
│ • Status: Queued for Batch 2 │ │ • Status: Queued for Batch 2 │
└───────────────────────────────┘ └───────────────────────────────┘
Registering VM Resources into an Environment
To onboard a target VM into an Environment:
- In Azure DevOps, navigate to Pipelines > Environments and create an environment (e.g.,
production-vms). - Select Add resource > Virtual machines, selecting the target operating system (Linux or Windows).
- Azure DevOps generates a registration script containing a personal access token (PAT) and organization URL.
- Execute the script on the target virtual machine with elevated privileges (
sudoor Administrator PowerShell). The script downloads and configures thevsts-agentas a local system service. - Assign metadata tags (e.g.,
role: web,tier: frontend,datacenter: eastus) directly via the Azure DevOps portal or the registration script.
The Rolling Deployment Strategy in YAML
The YAML strategy: rolling block controls progressive deployment execution across the registered VM fleet. Key parameters and lifecycle hooks include:
maxParallel: The number or percentage of virtual machines that can be updated simultaneously (e.g.,maxParallel: 2ormaxParallel: 25%).- Lifecycle Hooks:
preDeploy: Executes before the update starts (e.g., taking the VM out of the Azure Load Balancer backend pool).deploy: Steps that download artifacts, stop services, copy binaries, and start new application services.routeTraffic: Steps to re-attach the VM to the load balancer pool.postRouteTraffic: Executes automated health probes to verify application responsiveness under live traffic.on: failure: Executes remediation or alerts if any step in the lifecycle fails.
jobs:
- deployment: DeployWebFleet
displayName: 'Rolling Deployment to Web VM Fleet'
pool:
vmImage: 'ubuntu-latest'
environment:
name: 'production-vms'
resourceType: VirtualMachine
tags: 'role: web'
strategy:
rolling:
maxParallel: 2
preDeploy:
steps:
- script: echo 'Draining connections from load balancer...'
displayName: 'Drain Traffic'
deploy:
steps:
- download: current
artifact: drop
- script: |
sudo systemctl stop contoso-service
sudo cp -r $(Pipeline.Workspace)/drop/* /var/www/html/
sudo systemctl start contoso-service
displayName: 'Update Binaries and Restart Service'
postRouteTraffic:
steps:
- script: |
curl --fail http://localhost:80/health || exit 1
displayName: 'Local Smoke Test Health Probe'
3. Scripted Post-Deployment Operations & Automation Tasks
Automated pipelines require post-deployment scripting to execute database seed routines, purge Content Delivery Network (CDN) caches, update routing rules, and register API endpoints. Azure Pipelines provides two primary tasks for executing cloud-management scripts:
AzureCLI@2 vs. AzurePowerShell@5
| Attribute | AzureCLI@2 | AzurePowerShell@5 |
|---|---|---|
| Underlying Engine | Azure Command-Line Interface (az) | Azure PowerShell Az Modules (Get-AzResource) |
| Script Types | bash, pscore, batch, ps | pscore (PowerShell Core), ps (Desktop PS) |
| Cross-Platform | Native across Linux, macOS, and Windows | Native when configured with pwsh: true |
| Authentication | Automatic via ARM Service Connection | Automatic via ARM Service Connection |
| Best Fit | Cloud-native CLI automation, bash scripts | Deep .NET scripting, complex object pipelines |
Mission-Critical Pipeline Properties: pwsh: true and failOnStandardError: true
Two configuration settings are frequently tested on the AZ-400 exam because their omission leads to silent pipeline failures:
pwsh: true: Specifies that the script must run using PowerShell Core (pwsh) rather than Windows PowerShell (powershell.exe). This is mandatory when pipelines run on Microsoft-hosted Linux agents (ubuntu-latest) or self-hosted Linux containers. Ifpwsh: trueis omitted on a Linux agent, the pipeline attempts to invokepowershell.exeand fails immediately.failOnStandardError: true: By default, Azure Pipelines determines step success based on the process exit code (exit 0). However, many command-line utilities and legacy tools write fatal error descriptions tostderrwhile exiting with code 0. EnablingfailOnStandardError: trueinstructs the task runner to treat any data emitted to the standard error stream as an immediate task failure.
- task: AzureCLI@2
displayName: 'Purge Front Door Cache Post-Deploy'
inputs:
azureSubscription: 'ContosoServiceConnection'
scriptType: 'pscore'
scriptLocation: 'inlineScript'
failOnStandardError: true
inlineScript: |
$ErrorActionPreference = 'Stop'
Write-Host 'Purging Azure Front Door edge caches...'
az afd endpoint purge \
--resource-group rg-contoso-network \
--profile-name afd-contoso-global \
--endpoint-name ep-contoso-prod \
--content-paths '/*'
Engineering for Idempotency in Deployment Scripts
An automated deployment script is idempotent if running it multiple times produces the exact same end state without error, unintended side effects, or duplicate resource generation. Because CI/CD pipelines can be retried following transient network drops or agent timeouts, non-idempotent scripts cause catastrophic environment corruption.
- Anti-Pattern (Non-Idempotent):
# FAILS on rerun: Throws ResourceExists error az storage container create --name static-assets --account-name contosostorage # FAILS on rerun: Appends duplicate configuration lines every run echo 'export FEATURE_FLAG=true' >> /etc/environment - Idempotent Best Practice:
# Safe on rerun: Checks existence or uses declarative flags if ! az storage container show --name static-assets --account-name contosostorage > /dev/null 2>&1; then az storage container create --name static-assets --account-name contosostorage fi # Safe on rerun: Uses sed or grep to replace or insert once grep -qxF 'export FEATURE_FLAG=true' /etc/environment || echo 'export FEATURE_FLAG=true' >> /etc/environment
4. Comparison Table: Deployment Methodologies
| Deployment Mechanism | Target Platform | File Lock Safety | Cold Start Impact | Rollback Capability | Best Fit Workloads |
|---|---|---|---|---|---|
| Run From Package | App Service / Functions | 100% Safe (Read-only mount) | Fastest (Pre-indexed) | Instant (Swap package pointer) | C#, Java, Node.js web APIs & Functions |
| Zip Deploy | App Service / Functions | Vulnerable if app active | Moderate (Disk extraction) | Requires re-uploading zip | Small scripts, PHP, Python apps |
| Environment VM (Rolling) | IaaS Virtual Machines | Managed via service stop | Dependent on OS service | Phased re-deployment | Legacy Windows/Linux system services |
| Containerized Deploy | AKS / ACA / Web App | 100% Safe (Container layer) | Fast (Cached image layers) | Instant (Revision/pod switch) | Microservices, Dockerized architectures |
5. Realistic Exam Scenario & Common Traps
Scenario: Global Retail Web App Locked DLL Outages
Organization: Tailspin Toys hosts their primary e-commerce web platform on an Azure App Service Premium v3 plan running on Windows. Deployments run via an Azure Pipelines YAML pipeline utilizing the AzureWebApp@1 task.
- Problem: During high-volume holiday sales periods, CI/CD deployments intermittently fail with
ERROR_FILE_IN_USE: The process cannot access the file 'Tailspin.Commerce.dll' because it is being used by another process. When this happens, customer checkouts stall for 5–10 minutes while engineers manually restart the App Service. - Requirement: Modernize the deployment pipeline to ensure zero file locks during deployment, reduce application cold-start initialization latency, and ensure that post-deployment cache invalidation scripts written in PowerShell Core execute reliably on both Windows and Linux pipeline build agents.
DevOps Solution:
- Update the App Service configuration to set
WEBSITE_RUN_FROM_PACKAGE = 1. - Configure the
AzureWebApp@1task withdeploymentMethod: 'runFromPackage'. - Verify the application writes temporary session cache files to
%TEMP%rather thanwwwroot. - Update post-deployment cache purge tasks to use
AzurePowerShell@5withpwsh: trueandfailOnStandardError: true.
Common Exam Traps to Avoid
- Trap: Confusing Classic Deployment Groups with YAML Environments. Deployment Groups belong strictly to the Classic UI Release Pipelines. When designing modern YAML multi-stage pipelines, the correct resource is an Environment containing Virtual Machine resources.
- Trap: Forgetting that Run-From-Package renders
wwwrootread-only. If an exam scenario states that an application needs to generate log files or write dynamic image uploads intowwwroot,WEBSITE_RUN_FROM_PACKAGE=1will fail unless the code is modified to store uploads in Azure Blob Storage or write temp data to%TEMP%. - Trap: Omitting
pwsh: truewhen running PowerShell tasks on Linux agents. Windows PowerShell (powershell.exe) does not exist on Linux. Always configurepwsh: true(PowerShell Core) for cross-platform agent compatibility.
A company hosts an ASP.NET Core web application on Azure App Service. During frequent continuous deployment cycles, developers report that the deployment task fails intermittently with file access errors indicating that specific application DLLs are currently locked by the running w3wp.exe process. Furthermore, scaling out new instances during high load suffers from slow cold-start initialization times. Which configuration should the DevOps engineer implement?
A DevOps team is converting an existing Classic Release Pipeline into an Azure Pipelines multi-stage YAML pipeline. The deployment targets a fleet of 20 load-balanced Ubuntu virtual machines hosted across multiple Azure Availability Zones. The deployment must execute progressively across the virtual machine fleet, updating no more than two virtual machines at a time while running local verification tests before moving to the next batch. How should the team configure this in the YAML pipeline?
An automation engineer authors a post-deployment script inside an Azure Pipelines YAML pipeline to flush an Azure Front Door CDN profile and configure Redis cache keys. The pipeline executes on Microsoft-hosted Ubuntu Linux agents. During execution, the Azure CLI command writes a severe warning to standard error, but the pipeline step reports green success and continues, resulting in an un-invalidated cache in production. Furthermore, if the pipeline is retried, the script crashes because a Redis key already exists. What two modifications must be made?