06 - CI/CD (Dedicated)¶
This tutorial sets up CI/CD for Dedicated with standard zip deployment. Dedicated supports Kudu/SCM and zipdeploy workflows, which makes GitHub Actions integration straightforward.
Prerequisites¶
- Completed 05 - Infrastructure as Code
- Function App deployed on Dedicated
- Variables set:
export RG="rg-func-dedicated-dev"
export APP_NAME="func-dedi-<unique-suffix>"
export PLAN_NAME="asp-dedi-b1-dev"
export STORAGE_NAME="stdedidev<unique>"
export LOCATION="koreacentral"
What You'll Build¶
You will package the Python Function App from apps/python, deploy it with Zip Deploy and remote build settings, and implement an automated GitHub Actions deployment workflow.
Infrastructure Context
Plan: Dedicated (B1) | Network: Public internet in this tutorial | VNet: Supported by platform, not configured here
The app runs on a fixed App Service Plan (always on, no scale-to-zero). Basic B1 supports App Service VNet integration and private endpoints, but this guide uses Standard (S1+) for private networking scenarios to provide scale headroom, deployment slots, and a production-oriented validation path.
flowchart TD
INET[Internet] -->|HTTPS| FA[Function App\nDedicated B1-P3v3\nLinux Python 3.11]
subgraph VNET["VNet 10.0.0.0/16"]
subgraph INT_SUB["Integration Subnet 10.0.1.0/24\nDelegation: Microsoft.Web/serverFarms"]
FA
end
subgraph PE_SUB["Private Endpoint Subnet 10.0.2.0/24"]
PE_BLOB[PE: blob]
PE_QUEUE[PE: queue]
PE_TABLE[PE: table]
PE_FILE[PE: file]
end
end
PE_BLOB --> ST["Storage Account"]
PE_QUEUE --> ST
PE_TABLE --> ST
PE_FILE --> ST
subgraph DNS[Private DNS Zones]
DNS_BLOB[privatelink.blob.core.windows.net]
DNS_QUEUE[privatelink.queue.core.windows.net]
DNS_TABLE[privatelink.table.core.windows.net]
DNS_FILE[privatelink.file.core.windows.net]
end
PE_BLOB -.-> DNS_BLOB
PE_QUEUE -.-> DNS_QUEUE
PE_TABLE -.-> DNS_TABLE
PE_FILE -.-> DNS_FILE
FA -.->|System-Assigned MI| ENTRA[Microsoft Entra ID]
FA --> AI[Application Insights]
RFP["📦 WEBSITE_RUN_FROM_PACKAGE=1\nNo content share required"] -.- FA
ALWAYS_ON["⚙️ Always On: true\nFixed capacity"] -.- FA
style FA fill:#5c2d91,color:#fff
style VNET fill:#E8F5E9,stroke:#4CAF50
style ST fill:#FFF3E0
style DNS fill:#E3F2FD flowchart TD
A[GitHub push] --> B[GitHub Actions workflow]
B --> C["Build and deploy apps/python"]
C --> D[Dedicated Function App] Steps¶
Step 1 - Build a deployable zip package¶
python -m venv .venv
source .venv/bin/activate
pip install --requirement apps/python/requirements.txt
cd apps/python
zip --recurse-paths ../functionapp.zip .
cd ..
Step 2 - Deploy with zipdeploy¶
az functionapp config appsettings set \
--name $APP_NAME \
--resource-group $RG \
--settings \
SCM_DO_BUILD_DURING_DEPLOYMENT=true \
ENABLE_ORYX_BUILD=true
az functionapp deployment source config-zip \
--name $APP_NAME \
--resource-group $RG \
--src functionapp.zip
| CLI element | Explanation |
|---|---|
| Command(s) | az functionapp config appsettings set, az functionapp deployment source config-zip |
| Key flags | --name, --resource-group, --settings, --src |
| Variables | $APP_NAME, $RG |
| Expected result | Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing. |
CLI overrides SCM_DO_BUILD_DURING_DEPLOYMENT
The az functionapp deployment source config-zip command automatically sets SCM_DO_BUILD_DURING_DEPLOYMENT=false, which prevents pip install from running during deployment. For Python apps on Dedicated, prefer func azure functionapp publish $APP_NAME --python instead of manual zip deploy — it handles the remote build correctly.
Step 3 - Verify deployment and endpoint health¶
az functionapp deployment list-publishing-profiles \
--name $APP_NAME \
--resource-group $RG \
--output table
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health"
| CLI element | Explanation |
|---|---|
| Command(s) | az functionapp deployment list-publishing-profiles |
| Key flags | --name, --resource-group, --output, --request |
| Variables | $APP_NAME, $RG |
| Expected result | Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing. |
Step 4 - Add GitHub Actions workflow (recommended)¶
Create .github/workflows/deploy-dedicated.yml:
name: deploy-dedicated
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy Azure Functions app
uses: Azure/functions-action@v1
with:
app-name: ${{ vars.AZURE_FUNCTIONAPP_NAME }}
package: apps/python
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
scm-do-build-during-deployment: true
enable-oryx-build: true
Step 5 - Deployment slots (S1+ only)¶
Deployment slots are unavailable on B1. Upgrade to S1 or P1v2, then create and use a staging slot:
az appservice plan update \
--name $PLAN_NAME \
--resource-group $RG \
--sku S1
az functionapp deployment slot create \
--name $APP_NAME \
--resource-group $RG \
--slot staging
az functionapp deployment source config-zip \
--name $APP_NAME \
--resource-group $RG \
--slot staging \
--src functionapp.zip
az functionapp deployment slot swap \
--name $APP_NAME \
--resource-group $RG \
--slot staging \
--target-slot production
| CLI element | Explanation |
|---|---|
| Command(s) | az appservice plan update, az functionapp deployment slot create, az functionapp deployment source config-zip, az functionapp deployment slot swap |
| Key flags | --name, --resource-group, --sku, --slot, --src, --target-slot |
| Variables | $PLAN_NAME, $RG, $APP_NAME |
| Expected result | Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing. |
Requires Standard tier or higher
Deployment slots are not available on Basic (B1) tier. Upgrade to Standard (S1) or Premium (P1v2) before using slots.
Verification¶
az functionapp deployment source config-zip ...:
{
"active": true,
"author": "N/A",
"complete": true,
"deployer": "ZipDeploy",
"id": "xxxxxxxxxxxxxxxx",
"message": "Created via a push deployment",
"status": 4,
"url": "https://func-dedi-<unique-suffix>.scm.azurewebsites.net/api/deployments/xxxxxxxxxxxxxxxx"
}
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health":
az functionapp deployment slot create ... (after S1 upgrade):
{
"hostNames": [
"func-dedi-<unique-suffix>-staging.azurewebsites.net"
],
"name": "func-dedi-<unique-suffix>/slots/staging",
"state": "Running"
}
Next Steps¶
You now have a repeatable Dedicated deployment pipeline with optional slot-based release flow on S1/P1v2.
See Also¶
- Tutorial Overview & Plan Chooser
- Python Language Guide
- Platform: Hosting Plans
- Operations: Deployment
- Recipes Index