Blog Post

Apps on Azure Blog
7 MIN READ

Platform Improvements for Python AI Apps on Azure App Service

SurenderSinghMalik's avatar
May 14, 2026

Overview

Azure App Service (Linux) is a fully managed PaaS offering that supports a broad range of languages, including Python, Node.js, .NET, PHP, and Java. Developers can push source code or deploy a pre-built artifact; the platform handles the rest, including dependency installation, application containerization, and running the application at cloud scale. 

More customers are building intelligent applications using Azure AI Foundry and other AI services, and Python has become a language of choice for these workloads. The performance and reliability of the Python deployment pipeline directly shape the developer's experience on the platform, so we looked across the deployment path for opportunities to reduce latency and improve reliability. The first set of changes has reduced Python deployment latency on Azure App Service Linux by approximately 30%. This is the first step in a broader effort to make the platform better suited for AI application development, but the gains resulting from this effort will benefit all apps on the platform. Let's look at the details. 

Where Deployment Time Was Going 

Python web application deployments on Azure App Service Linux rely on Oryx, the platform's open-source build system, to produce runnable artifacts during remote builds. Platform telemetry showed that around 70% of Python app deployments use remote builds, and the majority of those resolve dependencies via requirements.txt using pip install. 

To understand where time was going, we profiled a stress workload: a 7.5 GB PyTorch application. Most production builds are smaller, but stress-testing a dependency-heavy application made the pipeline bottlenecks clear. 

When a Python app is deployed via remote build, the build container in Kudu (the App Service deployment service) runs Oryx to: 

  1. Extract the uploaded source code. 
  2. Create a Python virtual environment. 
  3. Install dependencies via pip install; 4.35 min (~34% of build time). 
  4. Copy files to a staging directory; 0.98 min (~8%). 
  5. Compress via tar + gzip into an archive; 7.53 min (~58%). 
  6. Write the archive to /home (Azure Storage SMB mount). 

The app container then extracts this archive to the local disk on every cold start. 

Why the Archive-Based Approach? 

The /home directory is backed by an Azure Storage SMB mount, where small-file I/O is comparatively expensive. Python dependencies are file-heavy: virtual environments commonly contain tens of thousands of files, and dependency-heavy ML applications can exceed 200,000 files. Writing those files individually over SMB would be prohibitively slow. Instead, the pipeline builds on the container's local filesystem, writes a single compressed archive over SMB, and the app container extracts it locally on startup for efficient module loading. 

Key insight: Compression was the single largest phase at 58% of build time, longer than installing the packages themselves. 

What We Changed 

Zstandard Compression (Replacing gzip) 

Standard gzip compression is single-threaded. In our benchmark, compression accounted for 58% of total build time, making it the dominant bottleneck. Because the archive is also decompressed during container startup, decompression time affects runtime startup latency as well. 

We evaluated three compression algorithms: gzip, LZ4, and Zstandard (zstd). The following results are averaged across multiple deployments of a 7.5 GB Python application with PyTorch and additional ML packages: 

Metric 

gzip 

LZ4 

zstd

Compression time 

7.53 min 

1.20 min 

1.18 min 

Decompression time 

2.80 min 

1.18 min 

1.07 min 

Archive size 

4.0 GB 

5.0 GB 

4.8 GB

 

Both zstd and LZ4 were more than 6× faster than gzip for compression and more than 2× faster for decompression. We selected zstd for the following reasons: 

  • Comparable speed to LZ4, with smaller archive sizes (4.8 GB vs. 5.0 GB). 
  • Mature ecosystem: zstd is based on RFC 8878 published in 2021 and ships with many common Linux distributions. 
  • Native tar support: tar –I zstd works out of the box; no extra packages required. 

Result: Compression time dropped from 7.53 min → 1.18 min (6.4× faster). Decompression improved from 2.80 min → 1.07 min (2.6× faster), directly reducing cold-start latency.

Faster Package Installation with uv 

pip is implemented in Python and has historically optimized compatibility over maximum parallelism. In dependency-heavy workloads, package download, resolution, and installation can become a major part of deployment time. In our 7.5 GB PyTorch benchmark, package installation accounted for ~34% of total build time (4.35 min out of 12.86 min). 

We introduced uv, a Python package manager written in Rust, as the primary installer for compatible requirements.txt deployments. Its uv pip install interface works with standard pip workflows. 

Fallback strategy: Compatibility remains the priority. When uv cannot handle a deployment, the platform retries with pip, preserving the behavior customers already depend on. 

Cache behavior: Package caches remain local to the build container. When the same app is deployed again before the kudu (build) container is recycled, both pip and uv can reuse cached packages and avoid repeated downloads.

Result: Package installation time dropped from 4.35 min → 1.50 min (3× faster).

Reducing File Copy Overhead 

A file copy showed up in two places. First, before compression, the build process copied the entire build directory (application code plus Python packages) to a staging location. This existed historically as a safety measure; creating a clean snapshot before tar reads the file tree. But the cost was steep for the large number of files inherent in Python dependencies. 

The fix was straightforward: create the tar archive directly from the build directory, skipping the intermediate copy entirely. 

Second, for pre-built deployment scenarios, we replaced the legacy Kudu sync path with Linux-native rsync. That gave us a better optimized tool for large Linux file trees and reduced the overhead of moving files into the final deployment location. Because this path is used beyond Python, the improvement benefits pre-built apps across the broader App Service Linux ecosystem. 

Result: Eliminated the 0.98-minute staging copy (8% of build time), reduced temporary disk usage, and improved the remaining file sync path. 

Pre-Built Python Wheels Cache 

We added a complementary optimization: a read-only cache of pre-built wheels for commonly used Python packages, selected using platform telemetry. The cache is mounted into the Kudu build container at runtime for Python workloads, allowing the installer to use local wheel artifacts before downloading packages externally. 

When a matching wheel is available, the installer uses it directly, avoiding a network fetch for that package. Cache misses fall back to the upstream registry (e.g., PyPI) as usual. 

The cache is managed by the platform and kept up to date, so supported Python builds can use it without any app change. 

Combined Results 

Controlled Benchmark (PyTorch 7.5 GB, P1mv3 App Service Tier) 

The following benchmark was measured on the P1mv3 App Service tier. Values in the "After" column reflect the optimized pipeline with zstd compression, uv package installation, direct tar creation, and the pre-built wheels cache enabled together. 

Phase 

Before 

After 

Improvement 

Package installation 

4.35 min 

1.50 min 

~3× faster 

File copy 

0.98 min 

0 min 

Eliminated 

Compression 

7.53 min 

1.18 min 

~6× faster 

Total build time 

12.86 min 

~2.68 min 

~79% reduction 

Production Fleet (All Python Linux Web Apps) 

Production telemetry across Python deployments shows the impact of these changes: deployment latency decreased by approximately 30% after the rollout. 

 

 

 

The controlled benchmark shows a larger improvement (~79%) because it exercises a dependency-heavy workload where package installation, file copy, and compression dominate total build time. Typical production apps are smaller and spend less time proportionally in those phases. 

Beyond Faster Builds: Reliability and Runtime Performance 

Faster builds only help when deployment requests reliably reach a worker that is ready to build. We updated the primary deployment clients Azure CLI, GitHub Actions, and Azure DevOps Pipelines to warm up Kudu before initiating deployments. Clients now issue a lightweight health-check request to the Kudu endpoint, helping ensure the deployment container is running and ready before the deployment begins. 

Clients also preserve affinity to the warmed-up worker using the ARR affinity cookie returned by the first request. This increases the chance that the deployment uses a worker with Kudu already running and local package caches already available from recent deployments. 

Together, these client-side changes reduced deployment failures from transient infrastructure issues and helped the pipeline optimizations reach the build phase reliably.

Result: Deployment failures caused by cold-start errors (502, 503, 499) dropped by ~30%.

We also improved the default runtime configuration for Python apps using the platform-provided Gunicorn startup path. Previously, the platform defaulted to a single worker, leaving most CPU cores idle. Now, it follows Gunicorn's recommended worker formula, fully utilizing available cores on multi-core SKUs and delivering higher request throughput out of the box.

workers = (2 × NUM_CORES) + 1

Key Takeaways 

  • Measure before optimizing: Platform telemetry showed that remote builds and requirements.txt based installs were the dominant Python deployment paths, which helped us focus on changes that would benefit the most customers. 
  • Compression was the biggest bottleneck: In the dependency-heavy benchmark, archive compression took longer than package installation. Replacing gzip with zstd reduced both build time and cold-start extraction time. 
  • File count matters: Python virtual environments can contain tens of thousands of files, and AI workloads can contain many more. Reducing unnecessary file copies and using Linux-native file sync helped lower overhead. 
  • Compatibility needs a fallback path: Introducing uv improved the common path, while falling back to pip preserved compatibility for apps that depend on existing Python packaging behavior. 
  • Deployment reliability is part of performance: Faster builds only help if deployment requests consistently reach a ready worker. Warm-up and worker affinity made the optimized path more reliable for customers. 
  • Beyond deployment: Runtime defaults, such as Gunicorn worker configuration, also affect how production apps perform once deployment is complete. 

Together, these changes made Python deployments faster and more reliable while preserving compatibility through safe fallbacks. We will continue improving the platform to make Azure App Service faster, more reliable, and better suited for AI application development.

Updated May 14, 2026
Version 1.0
No CommentsBe the first to comment