Python apps on Azure App Service for Linux just got a lot easier to build and ship! We’ve modernized the build pipeline to support new deployment options —whether you’re on classic setup.py, fully on pyproject.toml with Poetry or uv, or somewhere in between.
This post walks through five upgrades that reduce friction end-to-end—from local dev to GitHub Actions to the App Service build environment:
- pyproject.toml + uv (and poetry): modern, reproducible Python builds
- setup.py support
- .bashrc quality-of-life improvements in the App Service container shell
- GitHub Actions samples for common Python flows (setup.py, uv.lock, local venv, and pyproject.toml deployments)
pyproject.toml + uv
uv is an extremely fast Python package & project manager written in Rust—think “pip + virtualenv + pip-tools,” but much faster and with first-class project workflows. (Astral Docs)
On App Service for Linux: we’ve added automatic uv builds when your repo contains both pyproject.toml and uv.lock. That means reproducible installs with uv’s resolver—no extra switches needed.
What’s pyproject.toml?
It’s the standardized configuration for modern Python projects (PEP 621) where you declare metadata, dependencies, and your build backend. (Python Enhancement Proposals (PEPs))
Quickstart (new to uv?)
# in your project folder
pip install uv
uv init
uv init scaffolds a project and creates a pyproject.toml (and, for application projects, a sample main.py). Try it with uv run. (Astral Docs)
Add dependencies:
uv add flask
# add more as needed, e.g.:
# uv add requests pillow
A uv.lock file is generated to pin your dependency graph; uv then “syncs” from the lock for consistent installs. (Astral Docs)
A minimal pyproject.toml for a Flask app:
[project]
name = "uv-pyproject"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"flask>=3.1.2",
]
If you prefer to keep main.py
App Service’s default entry is app.py, so either rename main.py to app.py, or set a startup command:
uv run uvicorn main:app --host 0.0.0.0 --port 8000
Run locally:
uv run app.py
(uv run executes your script inside the project’s environment.) (Astral Docs)
Deploy to Azure App Service for Linux using your favorite method (e.g., azd up, GitHub Actions, or VS Code). During the build, you’ll see logs like:
Detected uv.lock (and no requirements.txt); creating virtual environment with uv...
Installing uv...
Requirement already satisfied: uv in /tmp/oryx/platforms/python/3.14.0/lib/python3.14/site-packages (0.9.7)
Executing: uv venv --link-mode=copy --system-site-packages antenv
Using CPython 3.14.0 interpreter at: /tmp/oryx/platforms/python/3.14.0/bin/python3.14
Creating virtual environment at: antenv
Activate with: source antenv/bin/activate
Activating virtual environment...
Detected uv.lock. Installing dependencies with uv...
Resolved 9 packages in 1ms
Installed 7 packages in 1.82s
+ blinker==1.9.0
+ click==8.3.0
+ flask==3.1.2
+ itsdangerous==2.2.0
+ jinja2==3.1.6
+ markupsafe==3.0.3
+ werkzeug==3.1.3Using pyproject.toml with Poetry
Already on Poetry? Great—Poetry uses pyproject.toml (typically with [tool.poetry] plus a poetry.lock) and complies with PEP-517/PEP-621. If your project is Poetry-managed, App Service’s pyproject.toml support applies just the same. For details on fields and build configuration, see Poetry’s official docs: the pyproject.toml reference and basic usage. (python-poetry.org)
Want to see a working uv example? Check the lowlight-enhancer-uv Flask app in our samples repo (deployable with azd up).
Support for setup.py
setup.py is the (Python) build/config script used by Setuptools to declare your project’s metadata and dependencies. Setuptools offers first-class support for setup.py, and it remains a valid way to package and install apps. (Setuptools)
Minimal setup.py for a Flask app
# setup.py
from setuptools import setup, find_packages
setup(
name="flask-app",
version="0.1.0",
packages=find_packages(exclude=("tests",)),
python_requires=">=3.14",
install_requires=[
"Flask>=3.1.2",
],
include_package_data=True,
)
Tip: install_requires and other fields are defined by Setuptools; see the keywords reference for what you can configure. (Setuptools)
What you’ll see during an App Service deployment
Python Version: /tmp/oryx/platforms/python/3.14.0/bin/python3.14
Creating directory for command manifest file if it does not exist
Removing existing manifest file
Python Virtual Environment: antenv
Creating virtual environment...
Executing: /tmp/oryx/platforms/python/3.14.0/bin/python3.14 -m venv antenv --copies
Activating virtual environment...
Running pip install setuptools...
Collecting setuptools
Downloading setuptools-80.9.0-py3-none-any.whl.metadata (6.6 kB)
Downloading setuptools-80.9.0-py3-none-any.whl (1.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 6.1 MB/s 0:00:00
Installing collected packages: setuptools
Successfully installed setuptools-80.9.0
...
Running python setup.py install...
[09:05:14+0000] Processing /tmp/8de1d13947ba65f
[09:05:14+0000] Installing build dependencies: started
[09:05:15+0000] Installing build dependencies: finished with status 'done'
[09:05:15+0000] Getting requirements to build wheel: started
[09:05:15+0000] Getting requirements to build wheel: finished with status 'done'
[09:05:15+0000] Preparing metadata (pyproject.toml): started
[09:05:15+0000] Preparing metadata (pyproject.toml): finished with status 'done'
Bash shell experience: friendlier .bashrc in SSH
We’ve started refreshing the SSH banner and shell behavior so it’s easier to orient yourself when you land in a Linux App Service container.
What changed (see screenshots below):
- Clear header with useful links. We now show both the general docs and a Python quickstart link right up front.
- Runtime at a glance. The header prints the Python version explicitly.
- Instance details for troubleshooting. You’ll see the Instance Name and Instance Id in the banner—handy when filing a support ticket or comparing logs across instances.
- No more noisy errors on login. Previously, the shell tried to auto-activate antenv and printed No such file or directory if it didn’t exist. The new logic checks first and shows a gentle tip instead.
|
|
|
What’s next
- More language-specific tips based on the detected stack.
- Shortcuts for common ssh tasks.
- Small UX touches (spacing, color, and prompts) to make SSH sessions feel consistent.
New GitHub Actions samples
We have also published a few Github Action sample workflows which makes it easy to deploy your Python apps and take advantage of our new features
- Deployment using pyproject.toml + uv - https://github.com/Azure/actions-workflow-samples/blob/master/AppService/Python-GHA-Samples/Python-PyProject-Uv-Sample.yml
- Deployment using Poetry - actions-workflow-samples/AppService/Python-GHA-Samples/Python-Poetry-Sample.yml at master · Azure/actions-workflow-samples
- Deployment using setup.py - actions-workflow-samples/AppService/Python-GHA-Samples/Python-SetupPy-Sample.yml at master · Azure/actions-workflow-samples
- Deployment python apps that are built locally - actions-workflow-samples/AppService/Python-GHA-Samples/Python-Local-Built-Deploy-Sample.yml at master · Azure/actions-workflow-samples
To use these templates
- Copy the relevant YAML into .github/workflows/ folder in your repo.
- Set auth: use OIDC with azure/login (or a service principal/publish profile if you must). (Microsoft Learn)
- Fill in inputs: app name, resource group, and sidecar details (image or extension parameters, env vars/ports).
- Commit & run: trigger on push or via Run workflow.
Conclusion
In the coming months, we’ll be announcing more improvements to Python on Azure App Service for Linux, focused on faster builds, better performance for AI workloads and clearer diagnostics. Try the flows that fit your team, and let us know what else would make your Python deployments even easier.