💡 Overview — Differences
The architectural contrast between Azure App Service and Azure Kubernetes Service (AKS) drives every CI/CD decision for Python deployments.
📑 Table of Contents
- 💡 Overview — Differences
- 🚀 CI/CD Pipeline Basics — Components
- 🏭 Deploy to Azure App Service — Process
- 🔧 Build and Publish Artifact
- 🚀 Deploy via Azure Web App
- 🐳 Deploy to AKS — Process
- 🔨 Build Docker Image
- 📦 Push and Deploy
- 📊 Comparison — Metrics
- 🟩 Final Thoughts
- ❓ Frequently Asked Questions
- Can I reuse the same Azure DevOps pipeline for both App Service and AKS?
- Do I need to write a Dockerfile for App Service?
- How does scaling differ between the two services?
- 📚 References & Further Reading
🚀 CI/CD Pipeline Basics — Components
A CI/CD pipeline for Python on Azure typically follows these stages:
- Source checkout – retrieve the repository.
- Build stage – install dependencies, run linting, execute unit tests.
- Package stage – create a wheel or Docker image.
-
Deploy stage – invoke Azure CLI or
kubectlto roll out the new version.
Across most production Azure DevOps workloads, a single YAML definition can be reused across environments by parameterizing the target service.
# azure-pipelines.yml
trigger: - main variables: - name: pythonVersion value: '3.11' stages: - stage: Build jobs: - job: Build pool: vmImage: 'ubuntu-latest' steps: - task: UsePythonVersion@0 inputs: versionSpec: $(pythonVersion) - script: | python -m pip install -upgrade pip pip install -r requirements.txt pytest -q displayName: 'Run tests' - script: | python -m pip wheel -no-deps -w dist . displayName: 'Build wheel' - publish: $(System.DefaultWorkingDirectory)/dist artifact: wheel
What this does: (More onPythonTPoint tutorials)
-
trigger: runs the pipeline on pushes to
main. - UsePythonVersion@0: pins the interpreter, preventing version drift between agents.
- pytest -q: executes unit tests; any failure aborts the pipeline.
- python -m pip wheel: creates a binary wheel that can be consumed directly by App Service or baked into a Docker image for AKS.
Key point: Parameterizing the deployment target keeps the CI logic DRY while supporting both App Service and AKS.
🏭 Deploy to Azure App Service — Process
Push a Python wheel to Azure App Service using the Azure CLI.
🔧 Build and Publish Artifact
The build stage already produced a .whl file. The next step uploads it to a private Azure Storage container that App Service can read. (Also read: ⚙️ Building a Jenkins Docker CI CD pipeline tutorial made easy)
$ az storage container create \ -account-name mystorageacct \ -name python-artifacts \ -public-access off
{ "created": true, "hasImmutabilityPolicy": false, "hasLegalHold": false
}
$ az storage blob upload \ -account-name mystorageacct \ -container-name python-artifacts \ -name myapp-1.0.0-py3-none-any.whl \ -file $(Pipeline.Workspace)/wheel/myapp-1.0.0-py3-none-any.whl
{ "etag": "\"0x8D9F7C3A2B5E9C5\"", "lastModified": "-09-15T12:34:56+00:00", "metadata": {}
}
What this does:
- az storage container create: creates a private container for the wheel.
- az storage blob upload: copies the wheel into the container, making it reachable via a SAS URL.
🚀 Deploy via Azure Web App
App Service installs the wheel at startup using a custom startup command. The CLI command sets the WEBSITE_RUN_FROM_PACKAGE app setting to the generated SAS URL.
$ SAS_URL=$(az storage blob generate-sas \ -account-name mystorageacct \ -container-name python-artifacts \ -name myapp-1.0.0-py3-none-any.whl \ -permissions r \ -expiry -01-01T00:00:00Z \ -output tsv)
$ az webapp config appsettings set \ -resource-group myResourceGroup \ -name myPythonApp \ -settings WEBSITE_RUN_FROM_PACKAGE=$SAS_URL
{ "properties": { "WEBSITE_RUN_FROM_PACKAGE": "https://mystorageacct.blob.core.windows.net/python-artifacts/myapp-1.0.0-py3-none-any.whl?sv=-11-02&ss;=b&srt;=sco&sp;=r&se;=-01-01T00:00:00Z&sig;=..." }
}
What this does:
- generate-sas: creates a time‑limited read‑only URL for the wheel.
- appsettings set: instructs App Service to download and install the wheel on each instance start.
Key point: Deploying a wheel avoids container image builds, reducing build time and storage costs compared with AKS.
🐳 Deploy to AKS — Process
Containerize the Python project and apply Kubernetes manifests to run it on AKS.
🔨 Build Docker Image
The Dockerfile builds a lightweight image based on python:3.11-slim and copies the wheel produced earlier. (Also read: 🐍 Azure App Service vs AKS for Django deployment — which one should you use?)
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY myapp-1.0.0-py3-none-any.whl .
RUN pip install -no-cache-dir myapp-1.0.0-py3-none-any.whl
CMD ["python", "-m", "myapp"]
What this does:
- FROM python:3.11-slim: uses a minimal base image (~85 MB) to keep the final size low.
- COPY … .: brings the wheel into the image.
- pip install: installs the wheel in an isolated environment.
-
CMD: launches the Python module when the container starts.
$ docker build -t myregistry.azurecr.io/myapp:1.0.0 .
Sending build context to Docker daemon 12.3MB
Step 1/5: FROM python:3.11-slim --> 2f2c9c7e6e5c
Step 2/5: WORKDIR /app --> Using cache --> 3b1d8f8a7c2e
Step 3/5: COPY myapp-1.0.0-py3-none-any.whl . --> 5d6e9f4c1a8b
Step 4/5: RUN pip install -no-cache-dir myapp-1.0.0-py3-none-any.whl --> Running in 7c9e2d1f4b6a
Successfully installed myapp-1.0.0
Removing intermediate container 7c9e2d1f4b6a --> a4f3c2d5e9f1
Step 5/5: CMD ["python", "-m", "myapp"] --> Running in 9b0d1e2a3c4f
Removing intermediate container 9b0d1e2a3c4f --> d3e7f2a9b0c1
Successfully built d3e7f2a9b0c1
Successfully tagged myregistry.azurecr.io/myapp:1.0.0
📦 Push and Deploy
After pushing the image to Azure Container Registry, a Kubernetes Deployment manifest defines the desired state.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: myapp-deployment
spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: myregistry.azurecr.io/myapp:1.0.0 ports: - containerPort: 8080 resources: limits: cpu: "500m" memory: "256Mi"
What this does:
- replicas: 3 – the Deployment controller maintains three pod instances, providing horizontal scaling.
-
resources.limits – enforces cgroup limits, preventing a runaway Python process from exhausting node memory.
$ az acr login -name myregistry
Login Succeeded.$ docker push myregistry.azurecr.io/myapp:1.0.0
The push refers to repository [myregistry.azurecr.io/myapp]
d3e7f2a9b0c1: Pushed
...
latest: digest: sha256:7c9e2d1f4b6a... size: 2365$ kubectl apply -f deployment.yaml
deployment.apps/myapp-deployment created$ kubectl get pods -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-deployment-7c9f5b6c9d-abcde 1/1 Running 0 12s
myapp-deployment-7c9f5b6c9d-fghij 1/1 Running 0 12s
myapp-deployment-7c9f5b6c9d-klmno 1/1 Running 0 12s
Key point: AKS requires a container image and a manifest; the extra layer grants control over replica count, resource limits, and rollout strategies unavailable in App Service.
📊 Comparison — Metrics
A side‑by‑side quantitative view of the two deployment targets for Python workloads.
| Aspect | App Service | AKS |
|---|---|---|
| Build time | ~2 min (wheel only) | ~5 min (Docker image) |
| Startup latency | ~1 s (cold start) | ~3 s (container pull) |
| Scaling granularity | Instance‑level (Azure autoscaler) | Pod‑level (Horizontal Pod Autoscaler) |
| Operational overhead | Low (no infra code) | Medium (manifests, Helm, RBAC) |
| Cost per vCPU‑hour | Higher (managed platform premium) | Lower (pay‑as‑you‑go nodes) |
Choosing the right target is a trade‑off between operational simplicity and fine‑grained control.
Key point: For a single‑service Python API with modest traffic, the App Service path reduces CI/CD complexity; for microservice ecosystems or custom networking, AKS provides the necessary flexibility.
🟩 Final Thoughts
Implementing ci cd python app service vs aks is not a binary decision; the pipeline can serve both targets by parameterizing the deployment step. The underlying mechanism—wheel distribution for App Service versus container image for AKS—determines build duration and runtime observability.
When rapid iteration and platform‑managed runtime are priorities, the App Service flow delivers the smallest CI/CD surface. When pod‑level scaling, custom sidecars, or multi‑service orchestration are required, the AKS route justifies the additional manifest and image layers. Align the choice with the operational model of your team, not with a perceived “newness” of Kubernetes.
❓ Frequently Asked Questions
Can I reuse the same Azure DevOps pipeline for both App Service and AKS?
Yes. Define a pipeline variable that selects either the App Service deployment block or the AKS manifest block, keeping CI stages identical and branching only at the final deploy step.
Do I need to write a Dockerfile for App Service?
No. App Service can run a Python wheel directly; Docker is required only when targeting AKS or a custom runtime not supported by App Service.
How does scaling differ between the two services?
App Service scales at the instance level using Azure’s built‑in autoscaler, which adds or removes whole instances based on CPU or request count. AKS scales at the pod level via the Horizontal Pod Autoscaler, allowing more granular allocation of CPU and memory.
💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.
📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.
📚 References & Further Reading
- Official Azure App Service docs – deployment and configuration guide: learn.microsoft.com
- Official Azure Kubernetes Service documentation – cluster and workload management: learn.microsoft.com

Top comments (0)